/*
  LICENSE
  -------
Copyright 2005 Nullsoft, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer. 

  * Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution. 

  * Neither the name of Nullsoft nor the names of its contributors may be used to 
    endorse or promote products derived from this software without specific prior written permission. 
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/
#include "milkdrop.h"
#include "resource.h"
#include "support.h"
#include "sound.h"


#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <time.h>
#include <math.h>
#include <io.h>

//#include <windowsx.h>
//#include <time.h>
//#include <math.h>
//#include <shellapi.h>
//#define _MT             // for multithreading
//  #include <process.h>    // for multithreading



extern CSoundData*   pg_sound;	// declared in main.cpp
extern CMilkDropObj* pg;		// declared in main.cpp
extern "C" CRITICAL_SECTION g_eval_cs;	// declared in eval.c
extern "C" BOOL g_eval_cs_valid;		// declared in eval.c

// from support.cpp:
extern bool g_bDebugOutput;
extern bool g_bDumpFileCleared;


// these callback functions are called by menu.cpp whenever the user finishes editing an eval_ expression.
void OnUserEditedPerFrame(LPARAM param1, LPARAM param2)
{
	pg->m_pState->RecompileExpressions(1);
}
void OnUserEditedPerPixel(LPARAM param1, LPARAM param2)
{
	pg->m_pState->RecompileExpressions(1);
}
void OnUserEditedPresetInit(LPARAM param1, LPARAM param2)
{
	pg->m_pState->RecompileExpressions(2);
}


/*
to do:

	-add dither to config panel
	-add "press f1 for help" message at startup; add checkbox to disable it to config panel
	-add option to point to new preset file
	-turn off SCROLL LOCK at startup.  Also protect vs. alt-tab and back...
	
	-carry on with new presets
	-(add imm-beat-driven sprites)
	-(onscreen menus for editing all controls???)

	-add blending for m_nWaveMode (via alpha weights)
	-speed up gamma, because it looks awesome!!!
		-with video echo + gamma -> render a+b into c, then render c many times (according to gamma)
	-smoothe waves (they're stairstepping at high res)
	-note: brighten would be cool sync'd to treble
		-so would *anything* controlled by an .eval()!!!
	-nukes the main winamp window for tom!!!

	1. what if some other process kills us - how will Finish() get called?
	2. look for setcursor mem leaks
	3. make it so you can ALT-TAB out and control Winamp (currently, minimizing winamp makes it inaccessible in this case)
		-alt: restore winamp on KILLFOCUS; minimize it on SETFOCUS
	4. don't include version # in class/app title!!!
	5. messageboxes don't show up if display mode already changed; probably because
		the window has been destroyed!

	-MORE WAVEFORMS
	-MORE MAP DISTORTIONS
    -bShowInverted
	-bShowHighContrast?
    -make dither a config panel option
	-beat detection & cloud bursts

    -keep eyes peeled for de-sync w/Scroll Lock and ALT-TABbing out of app

    -later: movable zoom center
	 
	RELEASE CHECKLIST
	-----------------
	-config panel:
		-check tab order 
		-make sure all items have tooltips
		-make sure all items are in the docs
	-check version # in the docs, on the config panel, and in the #define in milkdrop.h
	-comb preset collection
	-comb documentation
	

    CHANGES
    -------
		-added additive blending for waves
		-added stretchX, stretchY, spiralAmount
		-revamped sound analysis; results are now globally accessible, and all the code is in its own class
		-made some killer new presets
		-added text messages
			i shows current song title
			? shows current preset info.
			| shows current debug string
			f shows current fps (note this is lowercase f; capital F flips the 2nd graphics layer orientation)
			s (save preset) is now a total walkthrough; you can even enter a description for the preset
		-added cool stuff to config panel
			-user can now set the blend time between presets
			-added auto-preset-switching (you set the delay)
		-added preset-locking (to combat above feature)
			-use SCROLL LOCK to toggle on/off; the LED tells you if the current preset is locked or not
			-also, when you're in the middle of the save process, it won't auto-switch on you
		-separated INI file into two files (config vs. presets) 
			so that when people want to share presets, they're not overwriting their configs.
		-revamped keyboard layout
		-might have fixed bug w/minimization of Winamp window where just the Winamp
			playback window (not the eq/playlist/etc.) would stay hidden after 
			the plugin exited (Tom had this prob).
		-added 'press f1 for help' message at startup
		-set the default font to Monotype Corsiva
		-probably fixed the "disappearing text" bug (was releasing the DC before restoring the originally selected object)
		-added arbitrary functions to presets.  user can write something like:
			"zoom=zoom+0.05*sin(ang*3.0+time*0.97)" and it will do it for every vertex in the mesh,
			every frame.  They can have up to 4 of these.  Super massive thanks to Justin and Francis 
			for their "evallib" realtime expression evaluation code, which made this virtually effortless!  
		-ditched the 'spiral' preset parameter, since it's now easily reproduced with an expression.
		-dropped the max. mesh size to 48x36, since they all look good at 16x12 anyway
		-sped things up a bit by putting FPU in single-precision mode & disabling exceptions
		-presets are now individual files (didn't know there was a 64k INI file size limit!)
			-hitting 'L' now pops up a browseable list of named presets
				-'ENTER' selects
				-'ESC' exits the menu
				-'DEL' deletes the file (asks for confirmation first)
				-'R' renames the file
				-now allows left, right, home, end, DEL, and BACKSPACE keys
				-Insert mode works too
				-you can also hold SHIFT to select multiple chars, then do normal stuff with them,
					including cut/copy/paste.
				-you can also use [SHIFT]CTRL-LEFT, [SHIFT]CTRL-RIGHT to seek to the beginning of the prev/next word
			-'S' (save) now lets you type in a filename (instead of slot ##)
				-same fancy editing features available (cut,copy,paste)
		----------- 0.95d released to landoleet, 4/17/01
		-fixed dupes in config panel (g_iNumVidModes wasn't being reset to 0 on 2nd, 3rd run, etc.)
		-remembered that voodoo3 can only run in 16bpp
			-> made the default video mdoe 640x480x-16- (instead of any bit depth); fixed a bug w/"finding" it
		-fixed messageboxes by invoking them before Finish() gets called, and by adding the
			MB_SETFOREGROUND and MB_TOPMOST flags.  They now show up in whatever crazy video mode you're in.
		-got rid of static variables & global initializers (OOPS!  HOLY SHIT!)
			-might have fixed disappearing text problem (static int nLineHeight)
		-added 2nd font to config panel; now one is for song/preset/fps info (fancy); other is for menus, warnings, etc. (serious stuff)
		-beefed up message box for when D3D init fails -> suggests that they change their bit depth			
		-made default smoothing = 0.75
		-thoroughly tested matrox G450, winfast GeForce 2 MX, and Voodoo3 on win2k w/DX7 and DX8.
			-also grilled it on the multimon
			-results: <see text file>
		-made the circular waveform seamless
		-added a new waveform (replaced wave #1, which was a dupe)

		-made the global object into a pointer; it is now dynamically allocated
			(and initialized), as well as cleaned up, every time the config panel 
			or viz. effect is run.  
		-finished the menu system; fixed a few bugs
		-added a checkbox to fix pink- and white-color-saturation for 16-bit color on video cards that don't round down
		-consolidated waveforms (now 0..5)
		-fixed a bug with hitting ESC when saving a file -> kb input was messed afterwards
		-fixed a bug where you couldn't hit SPACE to change the preset after a successful save
		-fixed a bug where pressing left arrow from main menu would cause a crash (milkdrop would exit)
		-fixed a bug where when you hit ESC to abort when editing a float from the menu, it wouldn't restore
			the original value properly
		-added tooltips for menu navigation
		-added texture wrap option
		+added smooth blending of all math expressions, between presets, by computing both results
			and doing a final blend on the u/v coords
		+created GLOBAL equations for the user, which are executed only once a frame.
			-the old equations, executed per-vertex, are the LOCAL equations.
		-menus: now SHIFT-UP/DOWN will increment the value by 1/10 the normal amount
		-added tooltips to config panel
		-worked on the README file (vis_milk.txt)
		-the config panel now filters the list of video modes; it creates a temporary D3D device
			and checks out what bit depths it can use for rendering; then throws out all the video
			modes that don't jive with those bit depths.  Very rarely, the D3D device can't be created
			(ex: when voodoo3 is running w/windows display mode set to 32 bits) - in this case, no
			filtering is done.   This means that some video modes might display that are valid
			display modes for the cards, but might NOT work with D3D.  This is okay; in this rare case,
			when they actually try to run MilkDrop, they'll get a nice error message box telling them
			to choose a new color depth to fix the problem.
		-cranked default video mode up to 1024x768x16.
		-ditched q1..q4 from m_vars
		-revamped expressions; they can now be edited in multiline mode.  You have 1 big set of
			per-frame equations (separated by semicolons), and 1 big set of per-pixel equations.
		-when you start editing an expression, it now clears the old error message, if there was one (oops)
	<e-mail sent 5/5>
		-added decay to per-frame vars, so you can 'pulse' it now, using per-frame equations
		-added m_bDarkenCenter; also for brightness control; slightly dims the center of the image each frame
		-added CTRL+INS to do copy when editing strings (before just had CTRL+C)
		-menu now remembers the previous cursor position when you return to edit an equation again
		-removed the 5 waveform oscillators; that was the last of them; there are no more oscillators.  
			(they've all been replaced with math expressions).  Batch-processed old presets.
		-reverted to original eval.c (justin had changed it to return failure for unrecognized variables,
			but this is no longer the desired behavior, since we're now allowing the user to make up
			their own variables.)
		-fixed bug where the screen would be cleared on an auto preset change, if auto-preset-blend-time was 0.
	<v1.0 beta 1>
		-added a cute little star to the config panel (next to the 16-bit pink-bug-fix checkbox)
		-added 'frame' to m_vars
		-added timestamps to m_debug.txt (for the logging of windows messages)
		-ran boundschecker & found that CS for evallib wasn't being destroyed; fixed.
		-fixed display problem where you'd see a 1-pixel-wide line at the top & left edges of the screen,
			where the image was wrapping
		-added 'restore defaults' option to config panel
		-shaved off 4k by optimizing some things in ShowToUser(), DrawText(), and IdentifyWindowsMessage()
		-added critical section so that all kb/mouse/windows message input is thread-safe (vs. RenderFrame())
		-fixed 2 bugs with input variables for per-pixel equations:
			1. 'y' was coming in inverted (0..1 -> 1..0)
			2. 'ang' was being computed via atan2(a,b) and the x, y parameters were backwards
	<e-mail sent 5/10>
		-msgbox added at startup if no music playing
		-UpdatePresetList() now uses win32 findfile functions
		-CState::Export() now writes the file manually (instead of DeleteFile and then WritePrivateProfileString x 100)
		-These 2 things together seem to have solved 1 of the remaining 2 known bugs
			(where after you saved a preset, sometimes it wouldn't immediately show up in the file listing.)
		-gained about 1-1.5 fps by using DrawPrimitive instead of DrawIndexedPrimitive
			(this is because DrawIndexedPrimitive sends & transforms all the vertices every time you call
			it, even if you're only using a small subset of them to render an indexed strip, which I was.)
		-justin shaved 26k off the executable by using voodoo
		-fixed a bug on win2k with scroll lock
		-removed show preset info & show song titles from config panel
		-added song time & length (use F2 key)

*/



CMilkDropObj::CMilkDropObj()
{
	InitializeCriticalSection(&m_cs);
}

CMilkDropObj::~CMilkDropObj()
{
	
	if (g_eval_cs_valid)
	{
		//dumpmsg("Finish: deleting g_eval_cs");
		g_eval_cs_valid = false;
		DeleteCriticalSection(&g_eval_cs);
	}

	DeleteCriticalSection(&m_cs);

}

void CMilkDropObj::Randomize()
{
	srand(GetTickCount());
	//m_fAnimTime		= (rand() % 51234L)*0.01f;
	m_fRandStart[0]		= (rand() % 64841L)*0.01f;
	m_fRandStart[1]		= (rand() % 53751L)*0.01f;
	m_fRandStart[2]		= (rand() % 42661L)*0.01f;
	m_fRandStart[3]		= (rand() % 31571L)*0.01f;

	//CState temp;
	//temp.Randomize(rand() % NUM_MODES);
	//m_pState->StartBlend(&temp, m_fAnimTime, m_fBlendTimeUser);
}


void CMilkDropObj::BuildMenus()
{
	m_pCurMenu = &m_menuPreset;//&m_menuMain;
	
	m_menuPreset  .SetName("--edit current preset");
	m_menuWave    .SetName("--waveform");
	m_menuAugment .SetName("--augmentations");
	m_menuMotion  .SetName("--motion");
	m_menuPost    .SetName("--post-processing, global effects");
	m_menuSettings.SetName("--settings");

	// menu hierarchy
	 m_menuPreset.AddChildMenu(&m_menuWave);
	 m_menuPreset.AddChildMenu(&m_menuAugment);
	 m_menuPreset.AddChildMenu(&m_menuMotion);
	 m_menuPreset.AddChildMenu(&m_menuPost);

	// menu items
	m_menuWave.AddItem("wave type",         &m_pState->m_nWaveMode,     MENUITEMTYPE_INT, "each value represents a different way of drawing the waveform", 0, NUM_WAVES-1);
	m_menuWave.AddItem("size",              &m_pState->m_fWaveScale,    MENUITEMTYPE_LOGBLENDABLE, "relative size of the waveform");
	m_menuWave.AddItem("smoothing",         &m_pState->m_fWaveSmoothing,MENUITEMTYPE_BLENDABLE, "controls the smoothness of the waveform; 0=natural sound data (no smoothing), 0.9=max. smoothing", 0.0f, 0.9f);
	m_menuWave.AddItem("mystery parameter", &m_pState->m_fWaveParam,    MENUITEMTYPE_BLENDABLE, "what this one does is a secret (actually, its effect depends on the 'wave type'", -1.0f, 1.0f);
	m_menuWave.AddItem("opacity",           &m_pState->m_fWaveAlpha,    MENUITEMTYPE_LOGBLENDABLE, "opacity of the waveform; lower numbers = more transparent", 0.001f, 100.0f);
	m_menuWave.AddItem("position (X)",      &m_pState->m_fWaveX,        MENUITEMTYPE_BLENDABLE, "position of the waveform: 0 = far left edge of screen, 0.5 = center, 1 = far right", 0, 1);
	m_menuWave.AddItem("position (Y)",      &m_pState->m_fWaveY,        MENUITEMTYPE_BLENDABLE, "position of the waveform: 0 = very bottom of screen, 0.5 = center, 1 = top", 0, 1);
	m_menuWave.AddItem("color (red)",       &m_pState->m_fWaveR,        MENUITEMTYPE_BLENDABLE, "amount of red color in the wave (0..1)", 0, 1);
	m_menuWave.AddItem("color (green)",     &m_pState->m_fWaveG,        MENUITEMTYPE_BLENDABLE, "amount of green color in the wave (0..1)", 0, 1);
	m_menuWave.AddItem("color (blue)",      &m_pState->m_fWaveB,        MENUITEMTYPE_BLENDABLE, "amount of blue color in the wave (0..1)", 0, 1);
	m_menuWave.AddItem("use dots",          &m_pState->m_bWaveDots,     MENUITEMTYPE_BOOL, "if true, the waveform is drawn as dots (instead of lines)");
	m_menuWave.AddItem("draw thick",        &m_pState->m_bWaveThick,    MENUITEMTYPE_BOOL, "if true, the waveform is drawn as dots (instead of lines)");
	m_menuWave.AddItem("modulate opacity by volume", &m_pState->m_bModWaveAlphaByVolume,  MENUITEMTYPE_BOOL,      "if true, the waveform opacity is affected by the music's volume");
	m_menuWave.AddItem("modulation: transparent volume", &m_pState->m_fModWaveAlphaStart, MENUITEMTYPE_BLENDABLE, "when the relative volume hits this level, the wave becomes transparent.  1 = normal loudness, 0.5 = extremely quiet, 1.5 = extremely loud", 0.0f, 2.0f);
	m_menuWave.AddItem("modulation: opaque volume",   &m_pState->m_fModWaveAlphaEnd,      MENUITEMTYPE_BLENDABLE, "when the relative volume hits this level, the wave becomes opaque.  1 = normal loudness, 0.5 = extremely quiet, 1.5 = extremely loud", 0.0f, 2.0f);
	m_menuWave.AddItem("additive drawing",  &m_pState->m_bAdditiveWaves, MENUITEMTYPE_BOOL, "if true, the wave is drawn additively, saturating the image at white");
	m_menuWave.AddItem("color brightening", &m_pState->m_bMaximizeWaveColor, MENUITEMTYPE_BOOL, "if true, one of the color components (R/G/B) will always be forced to 1");

	m_menuAugment.AddItem("outer border thickness" ,&m_pState->m_fOuterBorderSize, MENUITEMTYPE_BLENDABLE, "thickness of the outer border drawn at the edges of the screen", 0, 0.5f);
	m_menuAugment.AddItem(" color (red)"           ,&m_pState->m_fOuterBorderR   , MENUITEMTYPE_BLENDABLE, "amount of red color in the outer border", 0, 1);
	m_menuAugment.AddItem(" color (green)"         ,&m_pState->m_fOuterBorderG   , MENUITEMTYPE_BLENDABLE, "amount of green color in the outer border", 0, 1);
	m_menuAugment.AddItem(" color (blue)"          ,&m_pState->m_fOuterBorderB   , MENUITEMTYPE_BLENDABLE, "amount of blue color in the outer border", 0, 1);
	m_menuAugment.AddItem(" opacity"               ,&m_pState->m_fOuterBorderA   , MENUITEMTYPE_BLENDABLE, "opacity of the outer border (0=transparent, 1=opaque)", 0, 1);
	m_menuAugment.AddItem("inner border thickness" ,&m_pState->m_fInnerBorderSize, MENUITEMTYPE_BLENDABLE, "thickness of the inner border drawn at the edges of the screen", 0, 0.5f);
	m_menuAugment.AddItem(" color (red)"           ,&m_pState->m_fInnerBorderR   , MENUITEMTYPE_BLENDABLE, "amount of red color in the inner border", 0, 1);
	m_menuAugment.AddItem(" color (green)"         ,&m_pState->m_fInnerBorderG   , MENUITEMTYPE_BLENDABLE, "amount of green color in the inner border", 0, 1);
	m_menuAugment.AddItem(" color (blue)"          ,&m_pState->m_fInnerBorderB   , MENUITEMTYPE_BLENDABLE, "amount of blue color in the inner border", 0, 1);
	m_menuAugment.AddItem(" opacity"               ,&m_pState->m_fInnerBorderA   , MENUITEMTYPE_BLENDABLE, "opacity of the inner border (0=transparent, 1=opaque)", 0, 1);
	m_menuAugment.AddItem("motion vector opacity"  ,&m_pState->m_fMvA            , MENUITEMTYPE_BLENDABLE, "opacity of the motion vectors (0=transparent, 1=opaque)", 0, 1);
	m_menuAugment.AddItem(" num. mot. vectors (X)" ,&m_pState->m_fMvX            , MENUITEMTYPE_BLENDABLE, "the number of motion vectors on the x-axis", 0, 64);
	m_menuAugment.AddItem(" num. mot. vectors (Y)" ,&m_pState->m_fMvY            , MENUITEMTYPE_BLENDABLE, "the number of motion vectors on the y-axis", 0, 48);
	m_menuAugment.AddItem(" offset (X)"            ,&m_pState->m_fMvDX           , MENUITEMTYPE_BLENDABLE, "horizontal placement offset of the motion vectors", -1, 1);
	m_menuAugment.AddItem(" offset (Y)"            ,&m_pState->m_fMvDY           , MENUITEMTYPE_BLENDABLE, "vertical placement offset of the motion vectors", -1, 1);
	m_menuAugment.AddItem(" trail length"          ,&m_pState->m_fMvL            , MENUITEMTYPE_BLENDABLE, "the length of the motion vectors (1=normal)", 0, 5);
	m_menuAugment.AddItem(" color (red)"           ,&m_pState->m_fMvR            , MENUITEMTYPE_BLENDABLE, "amount of red color in the motion vectors", 0, 1);
	m_menuAugment.AddItem(" color (green)"         ,&m_pState->m_fMvG            , MENUITEMTYPE_BLENDABLE, "amount of green color in the motion vectors", 0, 1);
	m_menuAugment.AddItem(" color (blue)"          ,&m_pState->m_fMvB            , MENUITEMTYPE_BLENDABLE, "amount of blue color in the motion vectors", 0, 1);

	m_menuMotion.AddItem("zoom amount",     &m_pState->m_fZoom,         MENUITEMTYPE_LOGBLENDABLE, "controls inward/outward motion.  0.9=zoom out, 1.0=no zoom, 1.1=zoom in");
	m_menuMotion.AddItem(" zoom exponent",  &m_pState->m_fZoomExponent, MENUITEMTYPE_LOGBLENDABLE, "controls the curvature of the zoom; 1=normal");
	m_menuMotion.AddItem("warp amount",     &m_pState->m_fWarpAmount,   MENUITEMTYPE_LOGBLENDABLE, "controls the magnitude of the warping; 0=none, 1=normal, 2=major warping...");
	m_menuMotion.AddItem(" warp scale",     &m_pState->m_fWarpScale,    MENUITEMTYPE_LOGBLENDABLE, "controls the wavelength of the warp; 1=normal, less=turbulent, more=smoother");
	m_menuMotion.AddItem(" warp speed",     &m_pState->m_fWarpAnimSpeed,MENUITEMTYPE_LOGFLOAT,     "controls the speed of the warp; 1=normal, less=slower, more=faster");
	m_menuMotion.AddItem("rotation amount",     &m_pState->m_fRot,      MENUITEMTYPE_BLENDABLE,    "controls the amount of rotation.  0=none, 0.1=slightly right, -0.1=slightly clockwise, 0.1=CCW", -1.00f, 1.00f);
	m_menuMotion.AddItem(" rot., center of (X)",&m_pState->m_fRotCX,    MENUITEMTYPE_BLENDABLE,    "controls where the center of rotation is, horizontally.  0=left, 0.5=center, 1=right", -1.0f, 2.0f);
	m_menuMotion.AddItem(" rot., center of (Y)",&m_pState->m_fRotCY,    MENUITEMTYPE_BLENDABLE,    "controls where the center of rotation is, vertically.  0=top, 0.5=center, 1=bottom", -1.0f, 2.0f);
	m_menuMotion.AddItem("translation (X)",     &m_pState->m_fXPush,    MENUITEMTYPE_BLENDABLE,    "controls amount of constant horizontal motion; -0.01 = slight shift right, 0=none, 0.01 = to left", -1.0f, 1.0f);
	m_menuMotion.AddItem("translation (Y)",     &m_pState->m_fYPush,    MENUITEMTYPE_BLENDABLE,    "controls amount of constant vertical motion; -0.01 = slight shift downward, 0=none, 0.01 = upward", -1.0f, 1.0f);
	m_menuMotion.AddItem("scaling (X)",         &m_pState->m_fStretchX, MENUITEMTYPE_LOGBLENDABLE, "controls amount of constant horizontal stretching; 0.99=shrink, 1=normal, 1.01=stretch");
	m_menuMotion.AddItem("scaling (Y)",         &m_pState->m_fStretchY, MENUITEMTYPE_LOGBLENDABLE, "controls amount of constant vertical stretching; 0.99=shrink, 1=normal, 1.01=stretch");

	// NOTE: all of the eval menuitems use a CALLBACK function to register the user's changes (see last param)
	m_menuPreset.AddItem("edit per_frame equations", &m_pState->m_szPerFrameExpr, MENUITEMTYPE_STRING, "read/write: -zoom rot warp cx cy dx dy sx sy decay; read-only: time, bass, mid, treb, bass_att, mid_att, treb_att", 256, 0, &OnUserEditedPerFrame);
	m_menuPreset.AddItem("edit per_pixel equations", &m_pState->m_szPerPixelExpr, MENUITEMTYPE_STRING, "read/write: -zoom rot warp cx cy dx dy sx sy; read-only: x, y, rad, ang, time, bass, mid, treb, bass_att, mid_att, treb_att", 256, 0, &OnUserEditedPerPixel);
	m_menuPreset.AddItem("edit preset initialization code", &m_pState->m_szPerFrameInit, MENUITEMTYPE_STRING, "read/write: -zoom rot warp cx cy dx dy sx sy decay; read-only: time, bass, mid, treb, bass_att, mid_att, treb_att", 256, 0, &OnUserEditedPresetInit);
	//m_menuPreset.AddItem("edit per_pixel initialization code", &m_pState->m_szPerPixelInit, MENUITEMTYPE_STRING, "read/write: -zoom rot warp cx cy dx dy sx sy; read-only: x, y, rad, ang, time, bass, mid, treb, bass_att, mid_att, treb_att", 256, 0, &OnUserEditedEvalFn);

	m_menuPost.AddItem("sustain level",           &m_pState->m_fDecay,                MENUITEMTYPE_BLENDABLE, "controls the eventual fade to black; 1=no fade, 0.9=strong fade", 0.90f, 1.0f);
	m_menuPost.AddItem("darken center",           &m_pState->m_bDarkenCenter,         MENUITEMTYPE_BOOL,      "when ON, help keeps the image from getting too bright by continually dimming the center point");
	m_menuPost.AddItem("gamma adjustment",        &m_pState->m_fGammaAdj,             MENUITEMTYPE_BLENDABLE, "controls brightness; 1=normal, 2=double, 3=triple, etc.", 1.0f, 8.0f);
	m_menuPost.AddItem("hue shader",              &m_pState->m_fShader,               MENUITEMTYPE_BLENDABLE, "adds subtle color variations to the image.  0=off, 1=fully on", 0.0f, 1.0f);
	m_menuPost.AddItem("video echo: alpha",       &m_pState->m_fVideoEchoAlpha,       MENUITEMTYPE_BLENDABLE, "controls the opacity of the second graphics layer; 0=transparent (off), 0.5=half-mix, 1=opaque", 0.0f, 1.0f);
	m_menuPost.AddItem(" video echo: scale",       &m_pState->m_fVideoEchoZoom,        MENUITEMTYPE_LOGBLENDABLE, "controls the size of the second graphics layer");
	m_menuPost.AddItem(" video echo: orientation", &m_pState->m_nVideoEchoOrientation, MENUITEMTYPE_INT, "selects an orientation for the second graphics layer.  0=normal, 1=flip on x, 2=flip on y, 3=flip on both", 0.0f, 3.0f);
	m_menuPost.AddItem("texture wrap",            &m_pState->m_bTexWrap,              MENUITEMTYPE_BOOL, "sets whether or not screen elements can drift off of one side and onto the other");
	m_menuPost.AddItem("stereo 3D",               &m_pState->m_bRedBlueStereo,        MENUITEMTYPE_BOOL, "displays the image in stereo 3D; you need 3D glasses (with red and blue lenses) for this.");
	m_menuPost.AddItem("filter: invert",          &m_pState->m_bInvert,        MENUITEMTYPE_BOOL, "inverts the colors in the image");
	m_menuPost.AddItem("filter: brighten",        &m_pState->m_bBrighten,      MENUITEMTYPE_BOOL, "brightens the darker parts of the image (nonlinear; square root filter)");
	m_menuPost.AddItem("filter: darken",          &m_pState->m_bDarken,        MENUITEMTYPE_BOOL, "darkens the brighter parts of the image (nonlinear; squaring filter)");
	m_menuPost.AddItem("filter: solarize",        &m_pState->m_bSolarize,      MENUITEMTYPE_BOOL, "emphasizes mid-range colors");
}



void CMilkDropObj::Init(HWND hWndParent, HINSTANCE hDllInstance)
{
	g_bDebugOutput		= false;
	g_bDumpFileCleared	= false;

	m_hMainWnd		= NULL;
	m_hWndParent	= NULL;
	m_hDllInstance	= NULL;

	m_pState    = &m_state_DO_NOT_USE[0];
	m_pOldState = &m_state_DO_NOT_USE[1];

	m_hTextWnd		= NULL;
	m_nTextWndWidth = 0;
	m_nTextWndHeight = 0;
	m_bSeparateTextWindow = false;
	m_bTextWindowClassRegistered = false;
	m_memDC			= NULL;
	m_memBM			= NULL;
	m_oldBM			= NULL;
	m_hBlackBrush	= NULL;

	m_nPresets		= 0;
	m_nDirs			= 0;
	m_nCurrentPreset = 0;
	m_szCurrentPresetFile[0] = 0;
	m_pPresetAddr	= NULL;
	m_pfPresetRating = NULL;
	m_szpresets		= new char[16384];
	m_nSizeOfPresetList =      16384;
	m_szPresetDir[0] = 0;

	m_nFrames		= 0;
	m_fAnimTime		= 0.0f;
	m_fStartTime	= 0.0f;
	m_nTimeHistoryPos = 0;
	m_fNextPresetTime = -1.0f;	// negative value means no time set (...it will be auto-set on first call to UpdateTime)
	m_supertext.bRedrawSuperText = false;
	m_supertext.fStartTime = -1.0f;
	m_fPresetStartTime = 0.0f;
	m_fps			= 0.0f;
	m_fFPSLimitSleep = 0.0f;		// milliseconds
	m_bMMX			= false;
	//m_bSSE          = false;
	m_bFirstRun		= true;
	m_bShowPressF1ForHelp = true;
	lstrcpy(m_szMonitorName, "[don't use multimon]");
	m_bShowMenuToolTips = true;	// NOTE: THIS IS CURRENTLY HARDWIRED TO TRUE - NO OPTION TO CHANGE
	m_n16BitGamma	= 2;
	m_bAutoGamma    = true;
	//m_bFixPinkBug	= true;
	//m_bForceRenderIn32Bits = false;

	m_bTexSizeWasAuto	= false;
	m_bPresetLockedByUser = false;
	m_bPresetLockedByCode = false;
	m_UI_mode			= UI_REGULAR;
	m_waitstring.bActive		= false;
	m_waitstring.bOvertypeMode = false;
	m_waitstring.szClipboard[0] = 0;
	m_nPlaylistCurPos	= 0;
	m_nPlaylistLength   = 0;
	m_szPlaylist    = NULL;
	m_nSongPosMS	= 0;
	m_nSongLenMS	= 0;
	m_nTrackPlaying	= 0;

	m_nTextHeightPixels = -1;
	m_nTextHeightPixels_Fancy = -1;
	m_bShowFPS			= false;
	m_bShowRating		= false;
	m_bShowPresetInfo	= false;
	m_bShowDebugInfo	= false;
	m_bShowSongTitle	= false;
	m_bShowSongTime		= false;
	m_bShowSongLen		= false;
	m_bShowHelpInfo		= false;
	m_fShowUserMessageUntilThisTime = -1.0f;
	m_fShowRatingUntilThisTime = -1.0f;
	m_szUserMessage[0]	= 0;
	m_szDebugMessage[0]	= 0;
	m_szSongMessage[0]	= 0;
	m_szSongTime[0]		= 0;

	m_nWidth			= 1024;
	m_nHeight			= 768;
	m_nDispBits			= 16;

	m_nTexSize			= -1;	// -1 means "auto"
	m_nGridX			= 24;
	m_nGridY			= 18;
	m_nFpsLimit			= -1;
	m_fTimeBetweenPresets		= 8.0f;
	m_fTimeBetweenPresetsRand	= 5.0f;
	m_bSequentialPresetOrder    = false;
	m_fBlendTimeUser			= 0.75f;
	m_fBlendTimeAuto			= 8.0f;
	m_bHardCutsDisabled			= false;
	m_fHardCutLoudnessThresh	= 2.5f;
	m_fHardCutHalflife			= 60.0f;
	m_cLeftEye3DColor[0]	= 0;
	m_cLeftEye3DColor[1]	= 255;
	m_cLeftEye3DColor[2]	= 255;
	m_cRightEye3DColor[0]	= 255;
	m_cRightEye3DColor[1]	= 0;
	m_cRightEye3DColor[2]	= 0;
	m_bEnableRating			= true;
	m_bSongTitleAnims		= true;
	m_fSongTitleAnimDuration = 1.7f;
	m_fTimeBetweenRandomSongTitles = -1.0f;
	m_fTimeBetweenRandomCustomMsgs = -1.0f;
	m_nSongTitlesSpawned = 0;
	m_nCustMsgsSpawned = 0;

	ZeroMemory(&m_guidMonitor, sizeof(GUID));
	m_bMonitorFound		= false;

	m_lpDD					= NULL;
	m_lpDDSPrimary			= NULL;   
	m_lpDDSBack				= NULL;
	m_lpDDSOverlay			= NULL;
	m_lpDDSOverlayBack		= NULL;
	m_lpSysMemBack[0]       = NULL;
	m_lpSysMemBack[1]       = NULL;
	m_lpVS[0]				= NULL;
	m_lpVS[1]				= NULL;
	m_lpDDSText				= NULL;
	m_lpDDSTitle[0]			= NULL;
	m_lpDDSTitle[1]			= NULL;
	m_bDisplayModeChanged	= false;
	//for (int k=0; k<8; k++) m_lpSprite[k] = NULL;

	m_pD3D					= NULL;
	m_lpD3DDev				= NULL; 
	ZeroMemory(&m_D3DDevDesc, sizeof(D3DDEVICEDESC7));
	m_verts					= NULL;
	m_vertinfo				= NULL;
	m_indices				= NULL;

	m_dwStartTickCount  = GetTickCount();
	m_dwPrevTickCount   = m_dwStartTickCount;
	for (int n=0; n<NUM_FONTS; n++)
	{
		m_szFontFace[n][0] = 0;
		m_bFontBold[n]	= false;
		m_bFontItalic[n] = false;
		m_nFontSize[n]	= 0;
		m_hfont[n] = NULL;
	}

	m_nNumericInputMode   = NUMERIC_INPUT_MODE_CUST_MSG;
	m_nNumericInputNum    = 0;
	m_nNumericInputDigits = 0;

	//for (n=0; n<NUM_TITLE_FONTS; n++)
	//	m_htitlefont[n] = NULL;
	
	m_szTitleFontFace[0] = 0;
	m_nTitleFontSize = 3;	// [0..4]  
	m_bTitleFontBold = false;
	m_bTitleFontItalic = true;

	m_bInitComplete		= false;
	m_bExiting			= false;
	m_bWinampMinimized	= false;
	m_bWindowClassRegistered = false;
	m_bDesktopMode		= false;
	 m_pDesktopModeScratchBits1_orig = NULL;
	 m_pDesktopModeScratchBits1_aligned = NULL;
	 m_bLostFocus		= false;
	 m_bSystrayReady	= false;
	 m_nDesktopProcSize = 1;		// 0 = full, 1 = half, 2 = quarter
	 m_szOldWallpaper[0] = 0;
	 m_dwOldBackgroundColor = 0;
	 m_bOldWallpaperKnown = false;
	 m_bWallpaperSet    = false;
	 m_key_R			= 15;	
	 m_key_G			= 0;	
	 m_key_B			= 15;	
	 m_bClearScreenAtStartup = true;
	 m_bAlways3D			= false;
	 m_bAlwaysOnTop			= false;
	 m_bWarningsDisabled    = false;
	 m_bWarningsDisabled2   = false;
	 m_bFixSlowText      = false;
	 m_bHardwareDesktopBlit = false;
	 //m_bDesktopModeSkipBurstCopy = false;
	 m_bTryRGBOverlayFirst = false;
	 m_bTryYUY2OverlayFirst = false;
	 m_bDesktopModeDoubleBuffered = false;
	 //m_bUseDXBlitAcrossBus = true;
	 m_xBusMode[0]      = XBUS_MMXCPY;
	 m_xBusMode[1]      = XBUS_NONE;
	m_bWindowed			= false;
	 m_bSizingWindow	= false;
	m_bOrigScrollLockState = false;
	m_fMotionVectorsTempDx = 0.0f;
	m_fMotionVectorsTempDy = 0.0f;

	srand(m_dwStartTickCount);

	sound.Init();

	BuildMenus();


	// --------------------other init--------------------

	// do shared init here (between config panel & fullscreen)
	char buf[256];

	m_hWndParent = hWndParent;
	m_hDllInstance = hDllInstance;
	
	// get path to INI file & read in prefs/settings right away, so DumpMsg works!
	GetModuleFileName(m_hDllInstance,m_szConfigIniFile,MAX_PATH);
	
	// convert to lowercase:
	for (int i=strlen(m_szConfigIniFile)-1; i>=0; i--)
		if (m_szConfigIniFile[i] >= 'A' && m_szConfigIniFile[i] <= 'Z')
			m_szConfigIniFile[i] += ('a' - 'A');

	char *p = m_szConfigIniFile + strlen(m_szConfigIniFile);
	while (p >= m_szConfigIniFile && *p != '\\') p--;
	if (++p >= m_szConfigIniFile) *p = 0;
	lstrcpy(m_szWinampPluginsPath,  m_szConfigIniFile);
	lstrcpy(m_szPresetDir,          m_szConfigIniFile);
	lstrcpy(m_szMsgIniFile,         m_szConfigIniFile);
	lstrcpy(m_szImgIniFile,         m_szConfigIniFile);
	lstrcat(m_szConfigIniFile, CONFIG_INIFILE);
	lstrcat(m_szMsgIniFile,    MSG_INIFILE);
	lstrcat(m_szImgIniFile,    IMG_INIFILE);
	lstrcat(m_szPresetDir, "milkdrop\\");

	m_bMMX = CheckMMXTechnology();
	//m_bSSE = CheckSSETechnology();
	
	ReadConfig();
	
	sprintf(buf, "Program version is %3.2f%c", CURRENT_VERSION*0.01f, (CURRENT_SUBVERSION==0) ? (' ') : ('a'+CURRENT_SUBVERSION-1));
	dumpmsg(buf);
	sprintf(buf, "Config.ini file is %s", m_szConfigIniFile);
	dumpmsg(buf);
	sprintf(buf, "Preset directory is %s", m_szPresetDir);
	dumpmsg(buf);
	sprintf(buf, "Parent window handle is %08x", m_hWndParent);
	dumpmsg(buf);
	sprintf(buf, "MMX %s detected.", m_bMMX ? "successfully " : "was NOT ");
	dumpmsg(buf);
	//sprintf(buf, "SSE %s detected.", m_bSSE ? "successfully " : "was NOT ");
	//dumpmsg(buf);
}

void CMilkDropObj::Finish()
{
	dumpmsg("Finish: starting cleanup...");
	
	m_bExiting = true;

	WriteRealtimeConfig();

	sound.Finish();

	SetScrollLock(m_bOrigScrollLockState);

	for (int n=0; n<NUM_FONTS; n++)
	{
		if (m_hfont[n])
		{
			DeleteObject(m_hfont[n]);
			m_hfont[n] = NULL;
		}
	}

	/*for (n=0; n<NUM_TITLE_FONTS; n++)
	{
		if (m_htitlefont[n])
		{
			DeleteObject(m_htitlefont[n]);
			m_htitlefont[n] = NULL;
		}
	}*/

	if (m_pPresetAddr) 
	{
		delete m_pPresetAddr;
		m_pPresetAddr = NULL;
	}

	if (m_pfPresetRating)
	{
		delete m_pfPresetRating;
		m_pfPresetRating = NULL;
	}

	if (m_szpresets)
	{
		delete m_szpresets;
		m_szpresets = NULL;
	}

	if (m_pD3D != NULL)
	{
		if( m_lpDDSPrimary != NULL )
		{
			if (m_lpD3DDev)
			{
				dumpmsg("Finish: releasing d3d device");
				m_lpD3DDev->Release();
				m_lpD3DDev = NULL;
				dumpmsg("Finish: d3d device released");
			}
			
			if (m_pD3D)
			{
				dumpmsg("Finish: releasing d3d interface");
				m_pD3D->Release();
				m_pD3D = NULL;
				dumpmsg("Finish: d3d interface released");
			}
		}
	}

	if (m_bDesktopMode)
    {
		RestoreWallpaper();
        Destroy_Overlay();
    }

    if( m_lpDD != NULL )
    {
		if (m_bDisplayModeChanged)
		{
			dumpmsg("Finish: restoring display mode");
	        m_lpDD->RestoreDisplayMode();
			m_bDisplayModeChanged = false;
			dumpmsg("Finish: display mode restored");
		}

		if (m_lpDDSTitle[0])
		{
			m_lpDDSTitle[0]->Release();
			m_lpDDSTitle[0] = NULL;
		}

		if (m_lpDDSTitle[1])
		{
			m_lpDDSTitle[1]->Release();
			m_lpDDSTitle[1] = NULL;
		}

		if (m_lpDDSText)
		{
			m_lpDDSText->Release();
			m_lpDDSText = NULL;
		}

		if( m_lpVS[0] != NULL )
		{
			m_lpVS[0]->Release();
			m_lpVS[0] = NULL;
		}

		if( m_lpVS[1] != NULL )
		{
			m_lpVS[1]->Release();
			m_lpVS[1] = NULL;
		}

		m_texmgr.Finish();

		if ( m_lpSysMemBack[0] != NULL )
		{
			m_lpSysMemBack[0]->Release();
			m_lpSysMemBack[0] = NULL;
		}

		if ( m_lpSysMemBack[1] != NULL )
		{
			m_lpSysMemBack[1]->Release();
			m_lpSysMemBack[1] = NULL;
		}

		if ((m_bWindowed || m_bDesktopMode) &&
			(m_lpDDSBack != NULL) )
		{
			dumpmsg("Finish: releasing back buffer");
			m_lpDDSBack->Release();
			m_lpDDSBack = NULL;
			dumpmsg("Finish: back buffer released");
		}

		if( m_lpDDSPrimary != NULL )
		{
			dumpmsg("Finish: releasing primary surface");
			m_lpDDSPrimary->Release();
			m_lpDDSPrimary = NULL;
			dumpmsg("Finish: primary surface released");
		}

        if (m_lpDD != NULL)
        {
			dumpmsg("Finish: releasing main directdraw object");
		    m_lpDD->Release();
		    m_lpDD = NULL;
			dumpmsg("Finish: main directdraw object released");
        }
    }

	if (m_szPlaylist)
	{
		delete [] m_szPlaylist;
		m_szPlaylist = NULL;
	}

	if (m_verts != NULL)
	{
		delete m_verts;
		m_verts = NULL;
	}

	if (m_verts_temp != NULL)
	{
		delete m_verts_temp;
		m_verts_temp = NULL;
	}

	if (m_vertinfo != NULL)
	{
		delete m_vertinfo;
		m_vertinfo = NULL;
	}

	if (m_indices != NULL)
	{
		delete m_indices;
		m_indices = NULL;
	}

	if (m_pDesktopModeScratchBits1_orig != NULL)
	{
		delete m_pDesktopModeScratchBits1_orig;
		m_pDesktopModeScratchBits1_orig = NULL;
		m_pDesktopModeScratchBits1_aligned = NULL;
	}

	// remove icon from taskbar
	if (m_bDesktopMode && m_bSystrayReady)
	{
		NOTIFYICONDATA nid = { sizeof(NOTIFYICONDATA) };
		nid.hWnd = m_hMainWnd;
		nid.uID = IDC_MILKDROP_SYSTRAY_ICON;
		Shell_NotifyIcon(NIM_DELETE, &nid);

		m_bSystrayReady = false;
	}	

	if (m_hBlackBrush)
	{
		DeleteObject(m_hBlackBrush);
	}

	if (m_memDC)
	{
		SelectObject(m_memDC, m_oldBM);	// delete our doublebuffer
		DeleteObject(m_memDC);
		DeleteObject(m_memBM);	
		m_memDC = NULL;
		m_memBM = NULL;
		m_oldBM = NULL;
	}

	if (m_hMainWnd)
	{
		dumpmsg("Finish: destroying main window");
		DestroyWindow(m_hMainWnd); // delete our window
		m_hMainWnd = NULL;
		dumpmsg("Finish: main window destroyed");
	}

	if (m_bWindowClassRegistered)
	{
		dumpmsg("Finish: unregistering window class");
		UnregisterClass(CLASSNAME,m_hDllInstance); // unregister window class
		m_bWindowClassRegistered = false;
		dumpmsg("Finish: window class unregistered");
	}

	if (m_hTextWnd)
	{
		dumpmsg("Finish: destroying text window");
		DestroyWindow(m_hTextWnd); 
		m_hTextWnd = NULL;
		dumpmsg("Finish: text window destroyed");
	}

	if (m_bTextWindowClassRegistered)
	{
		dumpmsg("Finish: unregistering text window class");
		UnregisterClass(TEXT_WINDOW_CLASSNAME,m_hDllInstance); // unregister window class
		m_bTextWindowClassRegistered = false;
		dumpmsg("Finish: text window class unregistered");
	}

	if (m_bWinampMinimized)
	{
		// restore Winamp window
		dumpmsg("Finish: restoring winamp main window");
		//WINDOWPLACEMENT wp = { sizeof(WINDOWPLACEMENT) };
		//wp.showCmd = SW_RESTORE;//g_oldWinampShowState;
		//SetWindowPlacement(m_hWndParent, &wp);
		ShowWindow(m_hWndParent, SW_RESTORE);
		m_bWinampMinimized = false;
	}

	//delete pg;
	//pg = NULL;

	dumpmsg("Finish: cleanup complete.");
}


void CMilkDropObj::ReadConfig()
{
	int n=0;

	m_bFirstRun		= !GetPrivateProfileBool("settings","bConfigured" ,false,m_szConfigIniFile);
	m_bEnableRating = GetPrivateProfileBool("settings","bEnableRating",m_bEnableRating,m_szConfigIniFile);
	m_bHardCutsDisabled = GetPrivateProfileBool("settings","bHardCutsDisabled",m_bHardCutsDisabled,m_szConfigIniFile);
	g_bDebugOutput	= GetPrivateProfileBool("settings","bDebugOutput",g_bDebugOutput,m_szConfigIniFile);
	m_bFontBold[0]		= GetPrivateProfileBool("settings","bFontBold",m_bFontBold[0],m_szConfigIniFile);
	m_bFontBold[1]		= GetPrivateProfileBool("settings","bFontBold2",m_bFontBold[1],m_szConfigIniFile);
	m_bFontBold[2]		= m_bFontBold[1];
	m_bFontItalic[0]	= GetPrivateProfileBool("settings","bFontItalic",m_bFontItalic[0],m_szConfigIniFile);
	m_bFontItalic[1]	= GetPrivateProfileBool("settings","bFontItalic2",m_bFontItalic[1],m_szConfigIniFile);
	m_bFontItalic[2]	= m_bFontItalic[1];
	m_bTitleFontBold	= GetPrivateProfileBool("settings","bTitleFontBold",m_bTitleFontBold,m_szConfigIniFile);
	m_bTitleFontItalic	= GetPrivateProfileBool("settings","bTitleFontItalic",m_bTitleFontItalic,m_szConfigIniFile);
	m_nTitleFontSize    = GetPrivateProfileInt("settings","nTitleFontSize"  ,m_nTitleFontSize,m_szConfigIniFile);
	//m_bShowSongInfo = GetPrivateProfileBool("settings","bShowSongInfo",m_bShowSongInfo,m_szConfigIniFile);
	//m_bShowPresetInfo=GetPrivateProfileBool("settings","bShowPresetInfo",m_bShowPresetInfo,m_szConfigIniFile);
	m_bShowPressF1ForHelp = GetPrivateProfileBool("settings","bShowPressF1ForHelp",m_bShowPressF1ForHelp,m_szConfigIniFile);
	//m_bShowMenuToolTips = GetPrivateProfileBool("settings","bShowMenuToolTips",m_bShowMenuToolTips,m_szConfigIniFile);
	m_bSongTitleAnims   = GetPrivateProfileBool("settings","bSongTitleAnims",m_bSongTitleAnims,m_szConfigIniFile);

	m_bShowFPS			= GetPrivateProfileBool("settings","bShowFPS",       m_bShowFPS			,m_szConfigIniFile);
	m_bShowRating		= GetPrivateProfileBool("settings","bShowRating",    m_bShowRating		,m_szConfigIniFile);
	m_bShowPresetInfo	= GetPrivateProfileBool("settings","bShowPresetInfo",m_bShowPresetInfo	,m_szConfigIniFile);
	//m_bShowDebugInfo	= GetPrivateProfileBool("settings","bShowDebugInfo", m_bShowDebugInfo	,m_szConfigIniFile);
	m_bShowSongTitle	= GetPrivateProfileBool("settings","bShowSongTitle", m_bShowSongTitle	,m_szConfigIniFile);
	m_bShowSongTime		= GetPrivateProfileBool("settings","bShowSongTime",  m_bShowSongTime	,m_szConfigIniFile);
	m_bShowSongLen		= GetPrivateProfileBool("settings","bShowSongLen",   m_bShowSongLen		,m_szConfigIniFile);
	//m_bShowHelpInfo	= GetPrivateProfileBool("settings","m_bShowHelpInfo	",m_bShowHelpInfo	,m_szConfigIniFile);

	//m_bFixPinkBug		= GetPrivateProfileBool("settings","bFixPinkBug",m_bFixPinkBug,m_szConfigIniFile);
	  int nTemp = GetPrivateProfileBool("settings","bFixPinkBug",-1,m_szConfigIniFile);
	  if (nTemp == 0)
		  m_n16BitGamma = 0;
	  else if (nTemp == 1)
		  m_n16BitGamma = 2;
	m_n16BitGamma		= GetPrivateProfileInt("settings","n16BitGamma",m_n16BitGamma,m_szConfigIniFile);
	m_bAutoGamma        = GetPrivateProfileBool("settings","bAutoGamma",m_bAutoGamma,m_szConfigIniFile);
	m_bWindowed	        = GetPrivateProfileBool("settings","bWindowed",m_bWindowed,m_szConfigIniFile);
	m_bDesktopMode		= GetPrivateProfileBool("settings","bDesktopMode",m_bDesktopMode,m_szConfigIniFile);
	m_nDesktopProcSize  = GetPrivateProfileInt("settings","nDesktopProcSize",m_nDesktopProcSize,m_szConfigIniFile);
	m_bHardwareDesktopBlit = GetPrivateProfileBool("settings","bHardwareDesktopBlit",m_bHardwareDesktopBlit,m_szConfigIniFile);
	m_bTryRGBOverlayFirst  = GetPrivateProfileBool("settings","bTryRGBOverlayFirst",m_bTryRGBOverlayFirst,m_szConfigIniFile);
	m_bTryYUY2OverlayFirst = GetPrivateProfileBool("settings","bTryYUY2OverlayFirst",m_bTryYUY2OverlayFirst,m_szConfigIniFile);
	//m_bDesktopModeSkipBurstCopy = GetPrivateProfileBool("settings","bDesktopModeSkipBurstCopy",m_bDesktopModeSkipBurstCopy,m_szConfigIniFile);
	m_xBusMode[0]       = GetPrivateProfileInt("settings","nDesktopModeVid2SysXferMode",m_xBusMode[0],m_szConfigIniFile);
	m_xBusMode[1]       = GetPrivateProfileInt("settings","nDesktopModeSys2VidXferMode",m_xBusMode[1],m_szConfigIniFile);
	m_bDesktopModeDoubleBuffered = GetPrivateProfileBool("settings","bDesktopModeDoubleBuffered",m_bDesktopModeDoubleBuffered,m_szConfigIniFile);
	m_nFpsLimit			= GetPrivateProfileInt("settings","nFpsLimit",m_nFpsLimit,m_szConfigIniFile);
	m_key_R				= GetPrivateProfileInt("settings","nColorKeyR",m_key_R,m_szConfigIniFile);
	m_key_G				= GetPrivateProfileInt("settings","nColorKeyG",m_key_G,m_szConfigIniFile);
	m_key_B				= GetPrivateProfileInt("settings","nColorKeyB",m_key_B,m_szConfigIniFile);
	m_bClearScreenAtStartup = GetPrivateProfileBool("settings","bClearScreenAtStartup",m_bClearScreenAtStartup,m_szConfigIniFile);
	m_bAlways3D				= GetPrivateProfileBool("settings","bAlways3D",m_bAlways3D,m_szConfigIniFile);
	m_bFixSlowText          = GetPrivateProfileBool("settings","bFixSlowText",m_bFixSlowText,m_szConfigIniFile);
	m_bAlwaysOnTop			= GetPrivateProfileBool("settings","bAlwaysOnTop",m_bAlwaysOnTop,m_szConfigIniFile);
	m_bWarningsDisabled		= GetPrivateProfileBool("settings","bWarningsDisabled",m_bWarningsDisabled,m_szConfigIniFile);
	m_bWarningsDisabled2    = GetPrivateProfileBool("settings","bWarningsDisabled2",m_bWarningsDisabled2,m_szConfigIniFile);

	m_cLeftEye3DColor[0] = GetPrivateProfileInt("settings","nLeftEye3DColorR",m_cLeftEye3DColor[0],m_szConfigIniFile);
	m_cLeftEye3DColor[1] = GetPrivateProfileInt("settings","nLeftEye3DColorG",m_cLeftEye3DColor[1],m_szConfigIniFile);
	m_cLeftEye3DColor[2] = GetPrivateProfileInt("settings","nLeftEye3DColorB",m_cLeftEye3DColor[2],m_szConfigIniFile);
	m_cRightEye3DColor[0] = GetPrivateProfileInt("settings","nRightEye3DColorR",m_cRightEye3DColor[0],m_szConfigIniFile);
	m_cRightEye3DColor[1] = GetPrivateProfileInt("settings","nRightEye3DColorG",m_cRightEye3DColor[1],m_szConfigIniFile);
	m_cRightEye3DColor[2] = GetPrivateProfileInt("settings","nRightEye3DColorB",m_cRightEye3DColor[2],m_szConfigIniFile);
	m_bSeparateTextWindow = GetPrivateProfileBool("settings","bSeparateTextWindow",m_bSeparateTextWindow,m_szConfigIniFile);

	m_nWidth	= GetPrivateProfileInt("settings","nWidth", m_nWidth ,m_szConfigIniFile);
	m_nHeight	= GetPrivateProfileInt("settings","nHeight",m_nHeight,m_szConfigIniFile);

	m_nDispBits		= GetPrivateProfileInt("settings","nDispBits"   ,m_nDispBits   ,m_szConfigIniFile);
#ifdef _NO_HW_
	m_nTexSize		= 128;//GetPrivateProfileInt("settings","nTexSize"    ,m_nTexSize    ,m_szConfigIniFile);
#else
	m_nTexSize		= GetPrivateProfileInt("settings","nTexSize"    ,m_nTexSize    ,m_szConfigIniFile);
#endif
		m_bTexSizeWasAuto = (m_nTexSize == -1);

	m_nFontSize[0]	= GetPrivateProfileInt("settings","nFontSize"   ,m_nFontSize[0],m_szConfigIniFile);
	m_nFontSize[1]	= GetPrivateProfileInt("settings","nFontSize2"  ,m_nFontSize[1],m_szConfigIniFile);
	m_nFontSize[2]  = m_nFontSize[1] - 2;
	m_nGridX		= GetPrivateProfileInt("settings","nMeshSize"   ,m_nGridX      ,m_szConfigIniFile);
	m_nGridY        = m_nGridX*3/4;
	
	m_fBlendTimeUser			= GetPrivateProfileFloat("settings","fBlendTimeUser"         ,m_fBlendTimeUser         ,m_szConfigIniFile);
	m_fBlendTimeAuto			= GetPrivateProfileFloat("settings","fBlendTimeAuto"         ,m_fBlendTimeAuto         ,m_szConfigIniFile);
	m_fTimeBetweenPresets		= GetPrivateProfileFloat("settings","fTimeBetweenPresets"    ,m_fTimeBetweenPresets    ,m_szConfigIniFile);
	m_fTimeBetweenPresetsRand	= GetPrivateProfileFloat("settings","fTimeBetweenPresetsRand",m_fTimeBetweenPresetsRand,m_szConfigIniFile);
	m_fHardCutLoudnessThresh	= GetPrivateProfileFloat("settings","fHardCutLoudnessThresh" ,m_fHardCutLoudnessThresh ,m_szConfigIniFile);
	m_fHardCutHalflife			= GetPrivateProfileFloat("settings","fHardCutHalflife"       ,m_fHardCutHalflife       ,m_szConfigIniFile);
	m_fSongTitleAnimDuration	= GetPrivateProfileFloat("settings","fSongTitleAnimDuration" ,m_fSongTitleAnimDuration ,m_szConfigIniFile);
	m_fTimeBetweenRandomSongTitles = GetPrivateProfileFloat("settings","fTimeBetweenRandomSongTitles" ,m_fTimeBetweenRandomSongTitles,m_szConfigIniFile);
	m_fTimeBetweenRandomCustomMsgs = GetPrivateProfileFloat("settings","fTimeBetweenRandomCustomMsgs" ,m_fTimeBetweenRandomCustomMsgs,m_szConfigIniFile);

	GetPrivateProfileString("settings","szFontFace","Monotype Corsiva",m_szFontFace[0],sizeof(m_szFontFace[0]), m_szConfigIniFile);
	GetPrivateProfileString("settings","szFontFace2","Arial",m_szFontFace[1],sizeof(m_szFontFace[1]), m_szConfigIniFile);
	lstrcpy(m_szFontFace[2], m_szFontFace[1]);
	GetPrivateProfileString("settings","szTitleFontFace","Times New Roman",m_szTitleFontFace,sizeof(m_szTitleFontFace), m_szConfigIniFile);
	GetPrivateProfileString("settings","szMonitorName","",m_szMonitorName,sizeof(m_szMonitorName), m_szConfigIniFile);
	GetPrivateProfileString("settings","szPresetDir",m_szPresetDir,m_szPresetDir,sizeof(m_szPresetDir),m_szConfigIniFile);

	ReadCustomMessages();

	// bounds-checking:
	if (m_nGridX > MAX_GRID_X)
		m_nGridX = MAX_GRID_X;
	if (m_nGridY > MAX_GRID_Y)
		m_nGridY = MAX_GRID_Y;
	if (m_fTimeBetweenPresetsRand < 0)
		m_fTimeBetweenPresetsRand = 0;
	if (m_fTimeBetweenPresets < 0.1f)
		m_fTimeBetweenPresets = 0.1f;
	if (m_bDesktopMode && m_bWindowed)	
		m_bDesktopMode = false;				// can't be in both these modes at once
}

void CMilkDropObj::WriteConfig()
{
	// constants:
	WritePrivateProfileString("settings","bConfigured","1",m_szConfigIniFile);
	
	WritePrivateProfileString("settings","szFontFace",m_szFontFace[0],m_szConfigIniFile);
	WritePrivateProfileString("settings","szFontFace2",m_szFontFace[1],m_szConfigIniFile);
	WritePrivateProfileString("settings","szTitleFontFace",m_szTitleFontFace,m_szConfigIniFile);
	WritePrivateProfileString("settings","szMonitorName",m_szMonitorName,m_szConfigIniFile);
	//note: m_szPresetDir is not written here; it is written manually, whenever it changes.
	
	char szSectionName[] = "settings";
	
	WriteInt(m_bSongTitleAnims,				"bSongTitleAnims",		m_szConfigIniFile, szSectionName);
	WriteInt(m_bHardCutsDisabled,			"bHardCutsDisabled",	m_szConfigIniFile, szSectionName);
	WriteInt(m_bEnableRating,				"bEnableRating",		m_szConfigIniFile, szSectionName);
	WriteInt(g_bDebugOutput,				"bDebugOutput",			m_szConfigIniFile, szSectionName);
	WriteInt(m_bFontBold[0],				"bFontBold",			m_szConfigIniFile, szSectionName);
	WriteInt(m_bFontBold[1],      			"bFontBold2",			m_szConfigIniFile, szSectionName);
	WriteInt(m_bFontItalic[0],    			"bFontItalic",			m_szConfigIniFile, szSectionName);
	WriteInt(m_bFontItalic[1],				"bFontItalic2",			m_szConfigIniFile, szSectionName);
	
	WriteInt(m_bTitleFontBold, 				"bTitleFontBold",		m_szConfigIniFile, szSectionName);
	WriteInt(m_bTitleFontItalic, 			"bTitleFontItalic",		m_szConfigIniFile, szSectionName);
	WriteInt(m_nTitleFontSize, 				"nTitleFontSize",		m_szConfigIniFile, szSectionName);

	//WriteInt(m_bShowPresetInfo, 			"bShowPresetInfo",		m_szConfigIniFile, szSectionName);
	//WriteInt(m_bShowSongInfo, 			"bShowSongInfo",		m_szConfigIniFile, szSectionName);
	//WriteInt(m_bFixPinkBug, 				"bFixPinkBug",			m_szConfigIniFile, szSectionName);

	WriteInt(m_bShowPressF1ForHelp, 		"bShowPressF1ForHelp",	m_szConfigIniFile, szSectionName);
	//WriteInt(m_bShowMenuToolTips, 			"bShowMenuToolTips",	m_szConfigIniFile, szSectionName);
	WriteInt(m_n16BitGamma, 				"n16BitGamma",			m_szConfigIniFile, szSectionName);
	WriteInt(m_bAutoGamma,  				"bAutoGamma",			m_szConfigIniFile, szSectionName);
	WriteInt(m_bWindowed, 					"bWindowed",			m_szConfigIniFile, szSectionName);
	WriteInt(m_bDesktopMode, 				"bDesktopMode",			m_szConfigIniFile, szSectionName);
	WriteInt(m_nDesktopProcSize, 			"nDesktopProcSize",		m_szConfigIniFile, szSectionName);
	WriteInt(m_bHardwareDesktopBlit, 		"bHardwareDesktopBlit",	m_szConfigIniFile, szSectionName);
	WriteInt(m_bTryRGBOverlayFirst, 		"bTryRGBOverlayFirst",	m_szConfigIniFile, szSectionName);
	WriteInt(m_bTryYUY2OverlayFirst, 		"bTryYUY2OverlayFirst",	m_szConfigIniFile, szSectionName);
	//WriteInt(m_bDesktopModeSkipBurstCopy, 	"bDesktopModeSkipBurstCopy",m_szConfigIniFile, szSectionName);
	WriteInt(m_xBusMode[0],					"nDesktopModeVid2SysXferMode",m_szConfigIniFile, szSectionName);
	WriteInt(m_xBusMode[1], 				"nDesktopModeSys2VidXferMode",m_szConfigIniFile, szSectionName);

	WriteInt(m_bDesktopModeDoubleBuffered,	"bDesktopModeDoubleBuffered",m_szConfigIniFile, szSectionName);
	WriteInt(m_nFpsLimit, 					"nFpsLimit",			m_szConfigIniFile, szSectionName);
	WriteInt(m_key_R, 						"nColorKeyR",			m_szConfigIniFile, szSectionName);
	WriteInt(m_key_G, 						"nColorKeyG",			m_szConfigIniFile, szSectionName);
	WriteInt(m_key_B, 						"nColorKeyB",			m_szConfigIniFile, szSectionName);
	WriteInt(m_bClearScreenAtStartup,		"bClearScreenAtStartup",m_szConfigIniFile, szSectionName);
	WriteInt(m_bAlways3D, 					"bAlways3D",			m_szConfigIniFile, szSectionName);
	WriteInt(m_bFixSlowText,				"bFixSlowText",			m_szConfigIniFile, szSectionName);
	WriteInt(m_bAlwaysOnTop,				"bAlwaysOnTop",			m_szConfigIniFile, szSectionName);
	WriteInt(m_bWarningsDisabled,			"bWarningsDisabled",	m_szConfigIniFile, szSectionName);
	WriteInt(m_bWarningsDisabled2,			"bWarningsDisabled2",	m_szConfigIniFile, szSectionName);
		
	WriteInt(m_cLeftEye3DColor[0], 			"nLeftEye3DColorR",		m_szConfigIniFile, szSectionName);
	WriteInt(m_cLeftEye3DColor[1], 			"nLeftEye3DColorG",		m_szConfigIniFile, szSectionName);
	WriteInt(m_cLeftEye3DColor[2], 			"nLeftEye3DColorB",		m_szConfigIniFile, szSectionName);
	WriteInt(m_cRightEye3DColor[0], 		"nRightEye3DColorR",	m_szConfigIniFile, szSectionName);
	WriteInt(m_cRightEye3DColor[1], 		"nRightEye3DColorG",	m_szConfigIniFile, szSectionName);
	WriteInt(m_cRightEye3DColor[2], 		"nRightEye3DColorB",	m_szConfigIniFile, szSectionName);

	WriteInt(m_bSeparateTextWindow, 		"bSeparateTextWindow",	m_szConfigIniFile, szSectionName);
											
	WriteInt(m_nWidth, 						"nWidth",				m_szConfigIniFile, szSectionName);
	WriteInt(m_nHeight, 					"nHeight",				m_szConfigIniFile, szSectionName);
	WriteInt(m_nDispBits, 					"nDispBits",			m_szConfigIniFile, szSectionName);
	WriteInt(m_nTexSize, 					"nTexSize",				m_szConfigIniFile, szSectionName);
	WriteInt(m_nFontSize[0], 				"nFontSize",			m_szConfigIniFile, szSectionName);
	WriteInt(m_nFontSize[1], 				"nFontSize2",			m_szConfigIniFile, szSectionName);
	WriteInt(m_nGridX, 						"nMeshSize",			m_szConfigIniFile, szSectionName);
	
	WriteFloat(m_fBlendTimeAuto, 			"fBlendTimeAuto",		m_szConfigIniFile, szSectionName);
	WriteFloat(m_fBlendTimeUser, 			"fBlendTimeUser",		m_szConfigIniFile, szSectionName);
	WriteFloat(m_fTimeBetweenPresets, 		"fTimeBetweenPresets",	m_szConfigIniFile, szSectionName);
	WriteFloat(m_fTimeBetweenPresetsRand, 	"fTimeBetweenPresetsRand",m_szConfigIniFile, szSectionName);
	WriteFloat(m_fHardCutLoudnessThresh, 	"fHardCutLoudnessThresh",m_szConfigIniFile, szSectionName);
	WriteFloat(m_fHardCutHalflife, 			"fHardCutHalflife",		m_szConfigIniFile, szSectionName);
	WriteFloat(m_fSongTitleAnimDuration, 	"fSongTitleAnimDuration",m_szConfigIniFile, szSectionName);
	WriteFloat(m_fTimeBetweenRandomSongTitles,"fTimeBetweenRandomSongTitles",m_szConfigIniFile, szSectionName);
	WriteFloat(m_fTimeBetweenRandomCustomMsgs,"fTimeBetweenRandomCustomMsgs",m_szConfigIniFile, szSectionName);
}


void CMilkDropObj::WriteRealtimeConfig()
{
	if (m_bSeparateTextWindow && m_hTextWnd)
	{
		RECT rect;
		if (GetWindowRect(m_hTextWnd, &rect))
		{
			WriteInt(rect.left,   "nTextWndLeft",   m_szConfigIniFile, "settings");
			WriteInt(rect.top,    "nTextWndTop",    m_szConfigIniFile, "settings");
			WriteInt(rect.right,  "nTextWndRight",  m_szConfigIniFile, "settings");
			WriteInt(rect.bottom, "nTextWndBottom", m_szConfigIniFile, "settings");
		}
	}

	if (m_bWindowed && m_hMainWnd)
	{
		RECT rect;
		if (GetWindowRect(m_hMainWnd, &rect))
		{
			WriteInt(rect.left,   "nMainWndLeft",   m_szConfigIniFile, "settings");
			WriteInt(rect.top,    "nMainWndTop",    m_szConfigIniFile, "settings");
			WriteInt(rect.right,  "nMainWndRight",  m_szConfigIniFile, "settings");
			WriteInt(rect.bottom, "nMainWndBottom", m_szConfigIniFile, "settings");
		}
	}

	WriteInt(m_bShowFPS			, "bShowFPS",        m_szConfigIniFile, "settings");
	WriteInt(m_bShowRating		, "bShowRating",     m_szConfigIniFile, "settings");
	WriteInt(m_bShowPresetInfo	, "bShowPresetInfo", m_szConfigIniFile, "settings");
	//WriteInt(m_bShowDebugInfo	, "bShowDebugInfo",  m_szConfigIniFile, "settings");
	WriteInt(m_bShowSongTitle	, "bShowSongTitle",  m_szConfigIniFile, "settings");
	WriteInt(m_bShowSongTime	, "bShowSongTime",   m_szConfigIniFile, "settings");
	WriteInt(m_bShowSongLen		, "bShowSongLen",    m_szConfigIniFile, "settings");
	//WriteInt(m_bShowHelpInfo	, "bShowHelpInfo",   m_szConfigIniFile, "settings");

}


void CMilkDropObj::ResetWindowSizeOnDisk()
{
	// reset the window size, in case they changed the display mode or sized the window too large
	WriteInt(DEFAULT_WINDOWED_MODE_TOP,    "nMainWndLeft",   m_szConfigIniFile, "settings");
	WriteInt(DEFAULT_WINDOWED_MODE_LEFT,   "nMainWndTop",    m_szConfigIniFile, "settings");
	WriteInt(DEFAULT_WINDOWED_MODE_RIGHT,  "nMainWndRight",  m_szConfigIniFile, "settings");
	WriteInt(DEFAULT_WINDOWED_MODE_BOTTOM, "nMainWndBottom", m_szConfigIniFile, "settings");
}


bool CMilkDropObj::GoFullScreen()
{
	m_pState->Default();
	LoadRandomPreset(0.0f);

	m_bOrigScrollLockState = (GetKeyState(VK_SCROLL) & 1);
	SetScrollLock(false);

	if (!CreateTheWindow()) return false;		// see milkdropfs.cpp
	if (!InitDDraw()) return false;				// see milkdropfs.cpp
	if (!InitD3D()) return false;				// see milkdropfs.cpp
	if (!InitFont()) return false;

	m_bInitComplete = true;

	return true;
}


bool CMilkDropObj::InitFont()
{
	m_nTextHeightPixels = -1;		// this will force the DrawText() function to re-calculate the height of a line of text
	m_nTextHeightPixels_Fancy = -1;

	for (int f=0; f<NUM_FONTS; f++)
	{
		int size = m_nFontSize[f];	// -1, 0, +1, etc.
		int w = (m_bSeparateTextWindow ? m_nTextWndWidth : m_nWidth);
		int h = (m_bSeparateTextWindow ? m_nTextWndHeight : m_nHeight);

		int wh_avg = (max(w,h*4/3)*3 + min(w,h*4/3)*1) / 4;

		if (wh_avg >= 1600)
			size += 3;
		else if (wh_avg >= 1024)	
			size += 2;
		else if (wh_avg >= 640)	
			size += 1;
		else						
			size += 0;

		if (size < -1) size = -1;
		if (size > 7) size = 7;
		
		if (m_hfont[f])
		{
			DeleteObject(m_hfont[f]);
			m_hfont[f] = NULL;
		}

		int fw, fh;

		switch(size)
		{
		case -1: fh = 12; fw =  4; break; 
		case  0: fh = 15; fw =  5; break; 
		case  1: fh = 18; fw =  8; break; 
		case  2: fh = 24; fw = 10; break; 
		case  3: fh = 32; fw = 12; break; 
		case  4: fh = 40; fw = 15; break; 
		case  5: fh = 48; fw = 18; break; 
		case  6: fh = 64; fw = 24; break; 
		case  7: fh = 96; fw = 36; break; 
		}

		m_hfont[f] = CreateFont(fh, fw, 0, 0, m_bFontBold[f] ? FW_BOLD : FW_LIGHT, m_bFontItalic[f], FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH, m_szFontFace[f]);		
	}

	// allocate the title fonts ONLY ONCE
	/*
	if (m_bSongTitleAnims)
	{
		int title_font_sizes[NUM_TITLE_FONTS] = { 3, 4, 6, 8, 10, 12, 14, 16,    18, 20, 24, 28, 32, 36, 40, 48,   56, 64, 72, 80, 96, 108, 120, 144,   160 };	
		assert(sizeof(title_font_sizes)/sizeof(int) == NUM_TITLE_FONTS);
		for (int n=0; n<NUM_TITLE_FONTS; n++)
		{
			if (!m_htitlefont[n])
			{
				m_htitlefont[n] = CreateFont(title_font_sizes[n], (int)ceil(title_font_sizes[n]*3/8), 0, 0, m_bTitleFontBold ? FW_BOLD : FW_LIGHT, m_bTitleFontItalic, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH, m_szTitleFontFace);
				if (!m_htitlefont[n]) 
				{
					char buf[512];
					sprintf(buf, "couldn't create title fonts");
					dumpmsg(buf); 
					MessageBox( NULL, buf, "MILKDROP ERROR", MB_OK|MB_SETFOREGROUND|MB_TOPMOST );
					Finish();
					return false;
				}
			}
		}
	}*/

	return true;
}


void CMilkDropObj::dumpmsg(char *s)
{
	g_dumpmsg(s);
}

void CMilkDropObj::LoadRandomPreset(float fBlendTime)
{
	// make sure file list is ok
	if (m_nPresets - m_nDirs == 0)
	{
		UpdatePresetList();
		if (m_nPresets - m_nDirs == 0)
		{
			// note: this error message is repeated in milkdropfs.cpp in DrawText()
			sprintf(m_szUserMessage, "ERROR: No preset files found in %s*.milk", m_szPresetDir);
			m_fShowUserMessageUntilThisTime = m_fAnimTime + 6.0f;
			return;
		}
	}
	
	// --TEMPORARY--
	// this comes in handy if you want to mass-modify a batch of presets;
	// just automatically tweak values in Import, then they immediately get exported to a .MILK in a new dir.
	/*
	for (int i=0; i<m_nPresets; i++)
	{
		char szPresetFile[512];
		lstrcpy(szPresetFile, m_szPresetDir);	// note: m_szPresetDir always ends with '\'
		lstrcat(szPresetFile, m_pPresetAddr[i]);
		//CState newstate;
		m_state2.Import("preset00", szPresetFile);

		lstrcpy(szPresetFile, "c:\\t7\\");
		lstrcat(szPresetFile, m_pPresetAddr[i]);
		m_state2.Export("preset00", szPresetFile);
	}
	*/
	// --[END]TEMPORARY--

	if (m_bSequentialPresetOrder)
	{
		m_nCurrentPreset++;
		if (m_nCurrentPreset < m_nDirs || m_nCurrentPreset >= m_nPresets)
			m_nCurrentPreset = m_nDirs;
	}
	else
	{
		// pick a random file
		if (!m_bEnableRating || (m_pfPresetRating[m_nPresets - 1] < 0.1f))
		{
			m_nCurrentPreset = m_nDirs + (rand() % (m_nPresets - m_nDirs));
		}
		else
		{
			float cdf_pos = (rand() % 14345)/14345.0f*m_pfPresetRating[m_nPresets - 1];

			/*
			char buf[512];
			sprintf(buf, "max = %f, rand = %f, \tvalues: ", m_pfPresetRating[m_nPresets - 1], cdf_pos);
			for (int i=m_nDirs; i<m_nPresets; i++)
			{
				char buf2[32];
				sprintf(buf2, "%3.1f ", m_pfPresetRating[i]);
				lstrcat(buf, buf2);
			}
			dumpmsg(buf);
			*/

			if (cdf_pos < m_pfPresetRating[m_nDirs])
			{
				m_nCurrentPreset = m_nDirs;
			}
			else
			{
				int lo = m_nDirs;
				int hi = m_nPresets;
				while (lo + 1 < hi)
				{
					int mid = (lo+hi)/2;
					if (m_pfPresetRating[mid] > cdf_pos)
						hi = mid;
					else
						lo = mid;
				}
				m_nCurrentPreset = hi;
			}
		}
	}

	// m_pPresetAddr[m_nCurrentPreset] points to the preset file to load (w/o the path);
	// first prepend the path, then load section [preset00] within that file
	lstrcpy(m_szCurrentPresetFile, m_szPresetDir);	// note: m_szPresetDir always ends with '\'
	lstrcat(m_szCurrentPresetFile, m_pPresetAddr[m_nCurrentPreset]);

	LoadPreset(m_szCurrentPresetFile, fBlendTime);
}

void CMilkDropObj::LoadPreset(char *szPresetFilename, float fBlendTime)
{
	if (szPresetFilename != m_szCurrentPresetFile)
		lstrcpy(m_szCurrentPresetFile, szPresetFilename);
	
	CState *temp = m_pState;
	m_pState = m_pOldState;
	m_pOldState = temp;

	m_pState->Import("preset00", m_szCurrentPresetFile);

	if (fBlendTime >= 0.001f)
		m_pState->StartBlendFrom(m_pOldState, m_fAnimTime, fBlendTime);

	m_fPresetStartTime = m_fAnimTime;
	m_fNextPresetTime = -1.0f;		// flags UpdateTime() to recompute this
}

void CMilkDropObj::SeekToPreset(char cStartChar)
{
	if (cStartChar >= 'a' && cStartChar <= 'z')
		cStartChar -= 'a' - 'A';

	for (int i = m_nDirs; i < m_nPresets; i++)
	{
		char ch = m_pPresetAddr[i][0];
		if (ch >= 'a' && ch <= 'z')
			ch -= 'a' - 'A';
		if (ch == cStartChar)
		{
			m_nPresetListCurPos = i;
			return;
		}
	}	
}


void CMilkDropObj::UpdatePresetList()
{
	//struct _finddata_t c_file;
	//long hFile;
	HANDLE hFindFile;

	char szMask[512];
	char szPath[512];
	char szLastPresetSelected[512];

	if (m_nPresetListCurPos >= m_nDirs && m_nPresetListCurPos < m_nPresets)
		lstrcpy(szLastPresetSelected, m_pPresetAddr[m_nPresetListCurPos]);
	else
		lstrcpy(szLastPresetSelected, "");

	// make sure the path exists; if not, go to winamp plugins dir
	if (GetFileAttributes(m_szPresetDir) == -1)
	{
		lstrcpy(m_szPresetDir, m_szWinampPluginsPath);
		if (GetFileAttributes(m_szPresetDir) == -1)
		{
			lstrcpy(m_szPresetDir, "c:\\");
		}
	}

	lstrcpy(szPath, m_szPresetDir);
	int len = strlen(szPath);
	if (len>0 && szPath[len-1] != '\\') 
	{
		lstrcat(szPath, "\\");
	}
	lstrcpy(szMask, szPath);
	//if (m_UI_mode == UI_CHANGEDIR)
		lstrcat(szMask, "*.*");
	//else
	//	lstrcat(szMask, "*.milk");


	WIN32_FIND_DATA ffd;
	ZeroMemory(&ffd, sizeof(ffd));
	
	for (int i=0; i<2; i++)		// usually RETURNs at end of first loop
	{
		m_nPresets = 0;
		m_nDirs    = 0;

		// find first .MILK file
		//if( (hFile = _findfirst(szMask, &c_file )) != -1L )		// note: returns filename -without- path
		if( (hFindFile = FindFirstFile(szMask, &ffd )) != INVALID_HANDLE_VALUE )		// note: returns filename -without- path
		{
			char *p = m_szpresets;
			int  bytes_left = m_nSizeOfPresetList - 1;		// save space for extra null-termination of last string
			
			/*
			len = strlen(ffd.cFileName);
			bytes_left -= len+1;
			lstrcpy(p, ffd.cFileName);
			p += len+1;
			m_nPresets++;
			*/
			//dumpmsg(ffd.cFileName);

			// find the rest
			//while (_findnext( hFile, &c_file ) == 0)
			do
			{
				bool bSkip = false;

				char szFilename[512];
				lstrcpy(szFilename, ffd.cFileName);

				if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				{
					// skip "." directory
					if (lstrcmp(ffd.cFileName, ".")==0)// || strlen(ffd.cFileName) < 1)
						bSkip = true;
					else
						sprintf(szFilename, "*%s", ffd.cFileName);
				}
				else
				{
					// skip normal files not ending in ".milk"
					int len = strlen(ffd.cFileName);
					if (len < 5 || lstrcmpi(ffd.cFileName + len - 5, ".milk") != 0)
						bSkip = true;					
				}

				if (!bSkip)
				{
					//dumpmsg(szFilename);
					len = strlen(szFilename);
					bytes_left -= len+1;
					
					m_nPresets++;
					if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 
						m_nDirs++;

					if (bytes_left >= 0)
					{
						lstrcpy(p, szFilename);
						p += len+1;
					}
				}
			}
			while (FindNextFile(hFindFile, &ffd));

			//_findclose( hFile );
			FindClose(hFindFile);

			if (bytes_left >= 0) 
			{
				// success!  double-null-terminate the last string.
				*p = 0;

				// also fill each slot of m_pPresetAddr with the address of each string
				// but do it in ALPHABETICAL ORDER
				if (m_pPresetAddr) delete m_pPresetAddr;
				m_pPresetAddr = new CHARPTR[m_nPresets];
				
				//the unsorted version:
				p = m_szpresets;
				for (int k=0; k<m_nPresets; k++)
				{
					m_pPresetAddr[k] = p;
					while (*p) p++;
					p++;
				}
				
				MergeSortPresets(0, m_nPresets-1);

				// finally, try to re-select the most recently-used preset in the list
				m_nPresetListCurPos = 0;
				if (szLastPresetSelected[0])
				{
					// try to automatically seek to the last preset loaded
					for (int i=m_nDirs; i<m_nPresets; i++)
						if (lstrcmp(szLastPresetSelected, m_pPresetAddr[i])==0)
							m_nPresetListCurPos = i; 
				}

				UpdatePresetRatings();

				// RETURN HERE - SUCCESS
				// RETURN HERE - SUCCESS
				// RETURN HERE - SUCCESS
				return;
			}
			else
			{
				// reallocate and do it again
				dumpmsg("too many presets -> reallocating list...");
				int new_size = (-bytes_left + m_nSizeOfPresetList)*11/10;	// overallocate a bit
				delete m_szpresets;
				m_szpresets = new char[new_size];
				m_nSizeOfPresetList = new_size;
			}
		}
	}

	// should never get here
	sprintf(m_szUserMessage, "Unfathomable error getting preset file list!");
	m_fShowUserMessageUntilThisTime = m_fAnimTime + 6.0f;
	
	m_nPresets = 0;
	m_nDirs = 0;
}


void CMilkDropObj::MergeSortPresets(int left, int right)
{
	// note: left..right range is inclusive
	int nItems = right-left+1;

	if (nItems > 2)
	{
		// recurse to sort 2 halves (but don't actually recurse on a half if it only has 1 element)
		int mid = (left+right)/2;
		/*if (mid   != left) */ MergeSortPresets(left, mid);
		/*if (mid+1 != right)*/ MergeSortPresets(mid+1, right);
				
		// then merge results
		int a = left;
		int b = mid + 1;
		while (a <= mid && b <= right)
		{
			bool bSwap;

			// merge the sorted arrays; give preference to strings that start with a '*' character
			int nSpecial = 0;
			if (m_pPresetAddr[a][0] == '*') nSpecial++;
			if (m_pPresetAddr[b][0] == '*') nSpecial++;

			if (nSpecial == 1)
			{
				bSwap = (m_pPresetAddr[b][0] == '*');
			}
			else
			{
				bSwap = (mystrcmpi(m_pPresetAddr[a], m_pPresetAddr[b]) > 0);
			}

			if (bSwap)
			{
				CHARPTR temp = m_pPresetAddr[b];
				for (int k=b; k>a; k--)
					m_pPresetAddr[k] = m_pPresetAddr[k-1];
				m_pPresetAddr[a] = temp;
				mid++;
				b++;
			}
			a++;
		}
	}
	else if (nItems == 2)
	{
		// sort 2 items; give preference to 'special' strings that start with a '*' character
		int nSpecial = 0;
		if (m_pPresetAddr[left][0] == '*') nSpecial++;
		if (m_pPresetAddr[right][0] == '*') nSpecial++;

		if (nSpecial == 1)
		{
			if (m_pPresetAddr[right][0] == '*')
			{
				CHARPTR temp = m_pPresetAddr[left];
				m_pPresetAddr[left] = m_pPresetAddr[right];
				m_pPresetAddr[right] = temp;
			}
		}
		else if (mystrcmpi(m_pPresetAddr[left], m_pPresetAddr[right]) > 0)
		{
			CHARPTR temp = m_pPresetAddr[left];
			m_pPresetAddr[left] = m_pPresetAddr[right];
			m_pPresetAddr[right] = temp;
		}
	}
}


void CMilkDropObj::WaitString_NukeSelection()
{
	if (m_waitstring.bActive &&
		m_waitstring.nSelAnchorPos != -1)
	{
		// nuke selection.  note: start & end are INCLUSIVE.
		int start = (m_waitstring.nCursorPos < m_waitstring.nSelAnchorPos) ? m_waitstring.nCursorPos : m_waitstring.nSelAnchorPos;
		int end   = (m_waitstring.nCursorPos > m_waitstring.nSelAnchorPos) ? m_waitstring.nCursorPos - 1 : m_waitstring.nSelAnchorPos - 1;
		int len   = strlen(m_waitstring.szText);
		int how_far_to_shift   = end - start + 1;
		int num_chars_to_shift = len - end;		// includes NULL char
		
		for (int i=0; i<num_chars_to_shift; i++)
			m_waitstring.szText[start + i] = m_waitstring.szText[start + i + how_far_to_shift];
		
		// clear selection
		m_waitstring.nCursorPos = start;
		m_waitstring.nSelAnchorPos = -1;
	}
}

void CMilkDropObj::WaitString_Cut()
{
	if (m_waitstring.bActive &&
		m_waitstring.nSelAnchorPos != -1)
	{
		WaitString_Copy();
		WaitString_NukeSelection();
	}
}

void CMilkDropObj::WaitString_Copy()
{
	if (m_waitstring.bActive &&
		m_waitstring.nSelAnchorPos != -1)
	{
		// note: start & end are INCLUSIVE.
		int start = (m_waitstring.nCursorPos < m_waitstring.nSelAnchorPos) ? m_waitstring.nCursorPos : m_waitstring.nSelAnchorPos;
		int end   = (m_waitstring.nCursorPos > m_waitstring.nSelAnchorPos) ? m_waitstring.nCursorPos - 1 : m_waitstring.nSelAnchorPos - 1;
		int chars_to_copy = end - start + 1;
		
		for (int i=0; i<chars_to_copy; i++)
			m_waitstring.szClipboard[i] = m_waitstring.szText[start + i];
		m_waitstring.szClipboard[chars_to_copy] = 0;
	}
}

void CMilkDropObj::WaitString_Paste()
{
	// NOTE: if there is a selection, it is wiped out, and replaced with the clipboard contents.
	
	if (m_waitstring.bActive)
	{
		WaitString_NukeSelection();

		int len = strlen(m_waitstring.szText);
		int chars_to_insert = strlen(m_waitstring.szClipboard);
		int i;

		if (len + chars_to_insert + 1 >= m_waitstring.nMaxLen)
		{
			chars_to_insert = m_waitstring.nMaxLen - len - 1;
			
			// inform user
			lstrcpy(m_szUserMessage, "(string too long)");
			m_fShowUserMessageUntilThisTime = m_fAnimTime + 2.5f;
		}
		else
		{
			m_fShowUserMessageUntilThisTime = m_fAnimTime;	// if there was an error message already, clear it
		}

		for (i=len; i >= m_waitstring.nCursorPos; i--)
			m_waitstring.szText[i + chars_to_insert] = m_waitstring.szText[i];
		for (i=0; i < chars_to_insert; i++)
			m_waitstring.szText[i + m_waitstring.nCursorPos] = m_waitstring.szClipboard[i];
		m_waitstring.nCursorPos += chars_to_insert;
	}
}

void CMilkDropObj::WaitString_SeekLeftWord()
{
	// move to beginning of prior word 

	while (m_waitstring.nCursorPos > 0 && 
		   !IsAlphanumericChar(m_waitstring.szText[m_waitstring.nCursorPos-1]))
	{
		m_waitstring.nCursorPos--;
	}

	while (m_waitstring.nCursorPos > 0 &&
		   IsAlphanumericChar(m_waitstring.szText[m_waitstring.nCursorPos-1]))
	{
		m_waitstring.nCursorPos--;
	}
}

void CMilkDropObj::WaitString_SeekRightWord()
{
	// move to beginning of next word
	
	//testing  lotsa   stuff  

	int len = strlen(m_waitstring.szText);

	while (m_waitstring.nCursorPos < len &&
		   IsAlphanumericChar(m_waitstring.szText[m_waitstring.nCursorPos]))
	{
		m_waitstring.nCursorPos++;
	}

	while (m_waitstring.nCursorPos < len &&
		   !IsAlphanumericChar(m_waitstring.szText[m_waitstring.nCursorPos]))
	{
		m_waitstring.nCursorPos++;
	}
}

int CMilkDropObj::WaitString_GetCursorColumn()
{
	if (m_waitstring.bDisplayAsCode)
	{
		int column = 0;
		while (m_waitstring.nCursorPos - column - 1 >= 0 &&
			   m_waitstring.szText[m_waitstring.nCursorPos - column - 1] != LINEFEED_CONTROL_CHAR)
		{
			column++;
		}
		return column;
	}
	else 
	{
		return m_waitstring.nCursorPos;
	}
}

int	CMilkDropObj::WaitString_GetLineLength()
{
	int line_start = m_waitstring.nCursorPos - WaitString_GetCursorColumn();
	int line_length = 0;

	while (m_waitstring.szText[line_start + line_length] != 0 &&
		   m_waitstring.szText[line_start + line_length] != LINEFEED_CONTROL_CHAR)
	{
		line_length++;
	}

	return line_length;
}

void CMilkDropObj::WaitString_SeekUpOneLine()
{
	int column = pg->WaitString_GetCursorColumn();

	if (column != m_waitstring.nCursorPos)
	{
		// seek to very end of previous line (cursor will be at the semicolon)
		m_waitstring.nCursorPos -= column + 1;
		
		int new_column = pg->WaitString_GetCursorColumn();

		if (new_column > column)
			m_waitstring.nCursorPos -= (new_column - column);
	}
}

void CMilkDropObj::WaitString_SeekDownOneLine()
{
	int column = pg->WaitString_GetCursorColumn();
	int newpos = m_waitstring.nCursorPos;

	while (m_waitstring.szText[newpos] != 0 && m_waitstring.szText[newpos] != LINEFEED_CONTROL_CHAR)
		newpos++;

	if (m_waitstring.szText[newpos] != 0)
	{
		m_waitstring.nCursorPos = newpos + 1;

		while (	column > 0 && 
				m_waitstring.szText[m_waitstring.nCursorPos] != LINEFEED_CONTROL_CHAR &&
				m_waitstring.szText[m_waitstring.nCursorPos] != 0)
		{
			m_waitstring.nCursorPos++;
			column--;
		}
	}
}


void CMilkDropObj::SavePresetAs(char *szNewFile)
{
	// overwrites the file if it was already there,
	// so you should check if the file exists first & prompt user to overwrite,
	//   before calling this function

	if (!m_pState->Export("preset00", szNewFile))
	{
		// error
		lstrcpy(m_szUserMessage, "ERROR: unable to save the file");
		m_fShowUserMessageUntilThisTime = m_fAnimTime + 6.0f;
	}
	else
	{
		// pop up confirmation
		lstrcpy(m_szUserMessage, "[save successful]");
		m_fShowUserMessageUntilThisTime = m_fAnimTime + 4.0f;

		// update m_pState->m_szDesc with the new name
		lstrcpy(m_pState->m_szDesc, m_waitstring.szText);

		// refresh file listing
		UpdatePresetList();
	}
}

void CMilkDropObj::DeletePresetFile(char *szDelFile)
{
	// NOTE: this function additionally assumes that m_nPresetListCurPos indicates 
	//		 the slot that the to-be-deleted preset occupies!

	// delete file
	if (!DeleteFile(szDelFile))
	{
		// error
		lstrcpy(m_szUserMessage, "ERROR: unable to delete delete the file");
		m_fShowUserMessageUntilThisTime = m_fAnimTime + 6.0f;
	}
	else
	{
		// pop up confirmation
		sprintf(m_szUserMessage, "[preset \"%s\" deleted]", m_pPresetAddr[m_nPresetListCurPos]);
		m_fShowUserMessageUntilThisTime = m_fAnimTime + 4.0f;

		// refresh file listing & re-select the next file after the one deleted

		if (m_nPresetListCurPos < m_nPresets - 1)
			m_nPresetListCurPos++;
		else if (m_nPresetListCurPos > 0)
			m_nPresetListCurPos--;
		else 
			m_nPresetListCurPos = 0;

		UpdatePresetList();
	}
}

void CMilkDropObj::RenamePresetFile(char *szOldFile, char *szNewFile)
{
	// NOTE: this function additionally assumes that m_nPresetListCurPos indicates 
	//		 the slot that the to-be-renamed preset occupies!

	if (GetFileAttributes(szNewFile) != -1)		// check if file already exists
	{
		// error
		lstrcpy(m_szUserMessage, "ERROR: a file already exists with that filename");
		m_fShowUserMessageUntilThisTime = m_fAnimTime + 6.0f;
		
		// (user remains in UI_LOAD_RENAME mode to try another filename)
	}
	else
	{
		// rename 
		if (!MoveFile(szOldFile, szNewFile))
		{
			// error
			lstrcpy(m_szUserMessage, "ERROR: unable to rename the file");
			m_fShowUserMessageUntilThisTime = m_fAnimTime + 6.0f;
		}
		else
		{
			// pop up confirmation
			lstrcpy(m_szUserMessage, "[rename successful]");
			m_fShowUserMessageUntilThisTime = m_fAnimTime + 4.0f;

			// if this preset was the active one, update m_pState->m_szDesc with the new name
			char buf[512];
			sprintf(buf, "%s.milk", m_pState->m_szDesc);
			if (lstrcmp(m_pPresetAddr[m_nPresetListCurPos], buf) == 0)
			{
				lstrcpy(m_pState->m_szDesc, m_waitstring.szText);
			}

			// refresh file listing & do a trick to make it re-select the renamed file
			lstrcpy(m_pPresetAddr[m_nPresetListCurPos], m_waitstring.szText);
			lstrcat(m_pPresetAddr[m_nPresetListCurPos], ".milk");
			UpdatePresetList();
		}

		// exit waitstring mode (return to load menu)
		m_UI_mode = UI_LOAD;
		m_waitstring.bActive = false;
	}
}


void CMilkDropObj::UpdatePresetRatings()
{
	if (!m_bEnableRating) 
		return;

	int k;

	// read in ratings for each preset:
	if (m_pfPresetRating) delete m_pfPresetRating;
	m_pfPresetRating = new float[m_nPresets];

	for (k=0; k<m_nDirs; k++)
	{
		m_pfPresetRating[k] = 0.0f;
	}

	for (k=m_nDirs; k<m_nPresets; k++)
	{
		char szFullPath[512];
		sprintf(szFullPath, "%s%s", m_szPresetDir, m_pPresetAddr[k]);
		float f = GetPrivateProfileFloat("preset00", "fRating", 3.0f, szFullPath);
		if (f < 0) f = 0;
		if (f > 5) f = 5;

		if (k==0)
			m_pfPresetRating[k] = f;
		else
			m_pfPresetRating[k] = m_pfPresetRating[k-1] + f;

		//char buf[256];
		//sprintf(buf, "%s: %f", szFullPath, m_pfPresetRating[k]);
		//dumpmsg(buf);
	}
}


void CMilkDropObj::SetCurrentPresetRating(float fNewRating)
{
	if (!m_bEnableRating)
		return;

	if (fNewRating < 0) fNewRating = 0;
	if (fNewRating > 5) fNewRating = 5;
	float change = (fNewRating - m_pState->m_fRating);

	// update the file on disk:
	//char szPresetFileNoPath[512];
	//char szPresetFileWithPath[512];
	//sprintf(szPresetFileNoPath,   "%s.milk", m_pState->m_szDesc);
	//sprintf(szPresetFileWithPath, "%s%s.milk", GetPresetDir(), m_pState->m_szDesc);
	WriteFloat(fNewRating, "fRating", m_szCurrentPresetFile, "preset00");

	// update the copy of the preset in memory
	m_pState->m_fRating = fNewRating;

	// update the cumulative internal listing:
	if (m_nCurrentPreset != -1)		// (can be -1 if dir. changed but no new preset was loaded yet)
		for (int i=m_nCurrentPreset; i<m_nPresets; i++)
			m_pfPresetRating[i] += change;

	/* keep in view:
		-test switching dirs w/o loading a preset, and trying to change the rating
			->m_nCurrentPreset is out of range!
		-soln: when adjusting rating: 
			1. file to modify is m_szCurrentPresetFile
			2. only update CDF if m_nCurrentPreset is not -1
		-> set m_nCurrentPreset to -1 whenever dir. changes
		-> set m_szCurrentPresetFile whenever you load a preset
	*/

	// show a message
	if (!m_bShowRating)
	{
		// see also: DrawText() in milkdropfs.cpp
		m_fShowRatingUntilThisTime = m_fAnimTime + 2.0f;
	}
}


void CMilkDropObj::UpdatePlaylist()
{
	int old_len = m_nPlaylistLength;
	m_nPlaylistLength = SendMessage(m_hWndParent,WM_USER, 0, 124);

	if (m_nPlaylistLength > old_len)
	{
		delete [] m_szPlaylist;
		m_szPlaylist = new td_playlist_entry[m_nPlaylistLength];
	}
	
	for (int i=0; i<m_nPlaylistLength; i++)
	{
		// copy w/&'s fixed for display
		char *str = (char*)SendMessage(m_hWndParent, WM_USER, i, 212);
		int ipos = 0;
		int opos = 0;
		while (str[ipos] && opos < 127)
		{
			//if (str[ipos] == '&')
			//	m_szPlaylist[i][opos++] = str[ipos];
			m_szPlaylist[i][opos++] = str[ipos++];
		}
		m_szPlaylist[i][opos] = 0;
	}
}

void CMilkDropObj::ReadCustomMessages()
{
	int n;

	// First, clear all old data
	for (n=0; n<MAX_CUSTOM_MESSAGE_FONTS; n++)
	{
		strcpy(m_CustomMessageFont[n].szFace, "arial");
		m_CustomMessageFont[n].bBold = false;
		m_CustomMessageFont[n].bItal = false;
		m_CustomMessageFont[n].nColorR = 255;
		m_CustomMessageFont[n].nColorG = 255;
		m_CustomMessageFont[n].nColorB = 255;
	}

	for (n=0; n<MAX_CUSTOM_MESSAGES; n++)
	{
		m_CustomMessage[n].szText[0] = 0;
		m_CustomMessage[n].nFont = 0;
		m_CustomMessage[n].fSize = 50.0f;  // [0..100]  note that size is not absolute, but relative to the size of the window
		m_CustomMessage[n].x = 0.5f;
		m_CustomMessage[n].y = 0.5f;
		m_CustomMessage[n].randx = 0;
		m_CustomMessage[n].randy = 0;
		m_CustomMessage[n].growth = 1.0f;
		m_CustomMessage[n].fTime = 1.5f;
		m_CustomMessage[n].fFade = 0.2f;

		m_CustomMessage[n].bOverrideBold = false;
		m_CustomMessage[n].bOverrideItal = false;
		m_CustomMessage[n].bOverrideFace = false;
		m_CustomMessage[n].bOverrideColorR = false;
		m_CustomMessage[n].bOverrideColorG = false;
		m_CustomMessage[n].bOverrideColorB = false;
		m_CustomMessage[n].bBold = false;
		m_CustomMessage[n].bItal = false;
		strcpy(m_CustomMessage[n].szFace, "arial");
		m_CustomMessage[n].nColorR = 255;
		m_CustomMessage[n].nColorG = 255;
		m_CustomMessage[n].nColorB = 255;
		m_CustomMessage[n].nRandR = 0;
		m_CustomMessage[n].nRandG = 0;
		m_CustomMessage[n].nRandB = 0;
	}

	// Then read in the new file
	for (n=0; n<MAX_CUSTOM_MESSAGE_FONTS; n++)
	{
		char szSectionName[32];
		sprintf(szSectionName, "font%02d", n);

		// get face, bold, italic, x, y for this custom message FONT
		GetPrivateProfileString(szSectionName,"face","arial",m_CustomMessageFont[n].szFace,sizeof(m_CustomMessageFont[n].szFace), m_szMsgIniFile);
		m_CustomMessageFont[n].bBold	= GetPrivateProfileBool(szSectionName,"bold",m_CustomMessageFont[n].bBold,  m_szMsgIniFile);
		m_CustomMessageFont[n].bItal	= GetPrivateProfileBool(szSectionName,"ital",m_CustomMessageFont[n].bItal,  m_szMsgIniFile);
		m_CustomMessageFont[n].nColorR  = GetPrivateProfileInt (szSectionName,"r"     ,m_CustomMessageFont[n].nColorR, m_szMsgIniFile);
		m_CustomMessageFont[n].nColorG  = GetPrivateProfileInt (szSectionName,"g"     ,m_CustomMessageFont[n].nColorG, m_szMsgIniFile);
		m_CustomMessageFont[n].nColorB  = GetPrivateProfileInt (szSectionName,"b"     ,m_CustomMessageFont[n].nColorB, m_szMsgIniFile);
	}

	for (n=0; n<MAX_CUSTOM_MESSAGES; n++)
	{
		char szSectionName[64];
		sprintf(szSectionName, "message%02d", n);

		// get fontID, size, text, etc. for this custom message:
		GetPrivateProfileString(szSectionName,"text","",m_CustomMessage[n].szText,sizeof(m_CustomMessage[n].szText), m_szMsgIniFile);
		m_CustomMessage[n].nFont	= GetPrivateProfileInt  (szSectionName,"font"  ,m_CustomMessage[n].nFont,   m_szMsgIniFile);
		m_CustomMessage[n].fSize	= GetPrivateProfileFloat(szSectionName,"size"  ,m_CustomMessage[n].fSize,   m_szMsgIniFile);
		m_CustomMessage[n].x		= GetPrivateProfileFloat(szSectionName,"x"     ,m_CustomMessage[n].x,       m_szMsgIniFile);
		m_CustomMessage[n].y		= GetPrivateProfileFloat(szSectionName,"y"     ,m_CustomMessage[n].y,       m_szMsgIniFile);
		m_CustomMessage[n].randx    = GetPrivateProfileFloat(szSectionName,"randx" ,m_CustomMessage[n].randx,   m_szMsgIniFile);
		m_CustomMessage[n].randy    = GetPrivateProfileFloat(szSectionName,"randy" ,m_CustomMessage[n].randy,   m_szMsgIniFile);

		m_CustomMessage[n].growth   = GetPrivateProfileFloat(szSectionName,"growth",m_CustomMessage[n].growth,  m_szMsgIniFile);
		m_CustomMessage[n].fTime    = GetPrivateProfileFloat(szSectionName,"time"  ,m_CustomMessage[n].fTime,   m_szMsgIniFile);
		m_CustomMessage[n].fFade    = GetPrivateProfileFloat(szSectionName,"fade"  ,m_CustomMessage[n].fFade,   m_szMsgIniFile);
		m_CustomMessage[n].nColorR  = GetPrivateProfileInt  (szSectionName,"r"     ,m_CustomMessage[n].nColorR, m_szMsgIniFile);
		m_CustomMessage[n].nColorG  = GetPrivateProfileInt  (szSectionName,"g"     ,m_CustomMessage[n].nColorG, m_szMsgIniFile);
		m_CustomMessage[n].nColorB  = GetPrivateProfileInt  (szSectionName,"b"     ,m_CustomMessage[n].nColorB, m_szMsgIniFile);
		m_CustomMessage[n].nRandR   = GetPrivateProfileInt  (szSectionName,"randr" ,m_CustomMessage[n].nRandR,  m_szMsgIniFile);
		m_CustomMessage[n].nRandG   = GetPrivateProfileInt  (szSectionName,"randg" ,m_CustomMessage[n].nRandG,  m_szMsgIniFile);
		m_CustomMessage[n].nRandB   = GetPrivateProfileInt  (szSectionName,"randb" ,m_CustomMessage[n].nRandB,  m_szMsgIniFile);

		// overrides: r,g,b,face,bold,ital
		GetPrivateProfileString(szSectionName,"face","",m_CustomMessage[n].szFace,sizeof(m_CustomMessage[n].szFace), m_szMsgIniFile);
		m_CustomMessage[n].bBold	= GetPrivateProfileInt (szSectionName, "bold", -1, m_szMsgIniFile);
		m_CustomMessage[n].bItal  	= GetPrivateProfileInt (szSectionName, "ital", -1, m_szMsgIniFile);
		m_CustomMessage[n].nColorR  = GetPrivateProfileInt (szSectionName, "r"   , -1, m_szMsgIniFile);
		m_CustomMessage[n].nColorG  = GetPrivateProfileInt (szSectionName, "g"   , -1, m_szMsgIniFile);
		m_CustomMessage[n].nColorB  = GetPrivateProfileInt (szSectionName, "b"   , -1, m_szMsgIniFile);

		m_CustomMessage[n].bOverrideFace   = (m_CustomMessage[n].szFace[0] != 0);
		m_CustomMessage[n].bOverrideBold   = (m_CustomMessage[n].bBold != -1);
		m_CustomMessage[n].bOverrideItal   = (m_CustomMessage[n].bItal != -1);
		m_CustomMessage[n].bOverrideColorR = (m_CustomMessage[n].nColorR != -1);
		m_CustomMessage[n].bOverrideColorG = (m_CustomMessage[n].nColorG != -1);
		m_CustomMessage[n].bOverrideColorB = (m_CustomMessage[n].nColorB != -1);
	}
}

void CMilkDropObj::LaunchCustomMessage(int nMsgNum)
{
	if (nMsgNum > 99)
		nMsgNum = 99;

	if (nMsgNum < 0)
	{
		int count=0;
		// choose randomly
		for (nMsgNum=0; nMsgNum<100; nMsgNum++)
			if (m_CustomMessage[nMsgNum].szText[0])
				count++;

		int sel = (rand()%count)+1;
		count = 0;
		for (nMsgNum=0; nMsgNum<100; nMsgNum++)
		{
			if (m_CustomMessage[nMsgNum].szText[0])
				count++;
			if (count==sel)
				break;
		}
	}

	
	if (nMsgNum < 0 || 
		nMsgNum >= MAX_CUSTOM_MESSAGES || 
		m_CustomMessage[nMsgNum].szText[0]==0)
	{
		return;
	}

	int fontID = m_CustomMessage[nMsgNum].nFont;

	m_supertext.bRedrawSuperText = true;
	m_supertext.bIsSongTitle = false;
	lstrcpy(m_supertext.szText, m_CustomMessage[nMsgNum].szText);

	// regular properties:
	m_supertext.fFontSize   = m_CustomMessage[nMsgNum].fSize;
	m_supertext.fX          = m_CustomMessage[nMsgNum].x + m_CustomMessage[nMsgNum].randx * ((rand()%1037)/1037.0f*2.0f - 1.0f);
	m_supertext.fY          = m_CustomMessage[nMsgNum].y + m_CustomMessage[nMsgNum].randy * ((rand()%1037)/1037.0f*2.0f - 1.0f);
	m_supertext.fGrowth     = m_CustomMessage[nMsgNum].growth;
	m_supertext.fDuration   = m_CustomMessage[nMsgNum].fTime;
	m_supertext.fFadeTime   = m_CustomMessage[nMsgNum].fFade;

	// overrideables:
	if (m_CustomMessage[nMsgNum].bOverrideFace)
		lstrcpy(m_supertext.nFontFace, m_CustomMessage[nMsgNum].szFace);
	else
		lstrcpy(m_supertext.nFontFace, m_CustomMessageFont[fontID].szFace);
	m_supertext.bItal   = (m_CustomMessage[nMsgNum].bOverrideItal) ? (m_CustomMessage[nMsgNum].bItal != 0) : (m_CustomMessageFont[fontID].bItal != 0);
	m_supertext.bBold   = (m_CustomMessage[nMsgNum].bOverrideBold) ? (m_CustomMessage[nMsgNum].bBold != 0) : (m_CustomMessageFont[fontID].bBold != 0);
	m_supertext.nColorR = (m_CustomMessage[nMsgNum].bOverrideColorR) ? m_CustomMessage[nMsgNum].nColorR : m_CustomMessageFont[fontID].nColorR;
	m_supertext.nColorG = (m_CustomMessage[nMsgNum].bOverrideColorG) ? m_CustomMessage[nMsgNum].nColorG : m_CustomMessageFont[fontID].nColorG;
	m_supertext.nColorB = (m_CustomMessage[nMsgNum].bOverrideColorB) ? m_CustomMessage[nMsgNum].nColorB : m_CustomMessageFont[fontID].nColorB;

	// randomize color
	m_supertext.nColorR += (int)(m_CustomMessage[nMsgNum].nRandR * ((rand()%1037)/1037.0f*2.0f - 1.0f));
	m_supertext.nColorG += (int)(m_CustomMessage[nMsgNum].nRandG * ((rand()%1037)/1037.0f*2.0f - 1.0f));
	m_supertext.nColorB += (int)(m_CustomMessage[nMsgNum].nRandB * ((rand()%1037)/1037.0f*2.0f - 1.0f));
	if (m_supertext.nColorR < 0) m_supertext.nColorR = 0;
	if (m_supertext.nColorG < 0) m_supertext.nColorG = 0;
	if (m_supertext.nColorB < 0) m_supertext.nColorB = 0;
	if (m_supertext.nColorR > 255) m_supertext.nColorR = 255;
	if (m_supertext.nColorG > 255) m_supertext.nColorG = 255;
	if (m_supertext.nColorB > 255) m_supertext.nColorB = 255;

	// fix &'s for display:
	/*
	{	
		int pos = 0;
		int len = strlen(m_supertext.szText);
		while (m_supertext.szText[pos] && pos<255)
		{
			if (m_supertext.szText[pos] == '&')
			{
				for (int x=len; x>=pos; x--)
					m_supertext.szText[x+1] = m_supertext.szText[x];
				len++;
				pos++;
			}
			pos++;
		}
	}*/

	m_supertext.fStartTime = m_fAnimTime; 
}

void CMilkDropObj::LaunchSongTitleAnim()
{
	m_supertext.bRedrawSuperText = true;
	m_supertext.bIsSongTitle = true;
	lstrcpy(m_supertext.szText, m_szSongMessage);
	lstrcpy(m_supertext.nFontFace, m_szTitleFontFace);
	m_supertext.fFontSize   = (float)m_nTitleFontSize;
	m_supertext.bBold       = m_bTitleFontBold;
	m_supertext.bItal       = m_bTitleFontItalic;
	m_supertext.fX          = 0.5f;
	m_supertext.fY          = 0.5f;
	m_supertext.fGrowth     = 1.0f;
	m_supertext.fDuration   = m_fSongTitleAnimDuration;
	m_supertext.nColorR     = 255;
	m_supertext.nColorG     = 255;
	m_supertext.nColorB     = 255;

	m_supertext.fStartTime = m_fAnimTime; 
}

bool CMilkDropObj::LaunchSprite(int nSpriteNum, int nSlot)
{
	char initcode[8192], code[8192], img[512], section[64];
	char szTemp[8192];

	initcode[0] = 0;
	code[0] = 0;
	img[0] = 0;
	sprintf(section, "img%02d", nSpriteNum);

	// 1. read in image filename
	GetPrivateProfileString(section, "img", "", img, sizeof(img)-1, m_szImgIniFile);
	if (img[0] == 0)
	{
		sprintf(m_szUserMessage, "sprite #%d error: couldn't find 'img=' setting, or sprite is not defined", nSpriteNum); 
		m_fShowUserMessageUntilThisTime = m_fAnimTime + 7.0f;
		return false;
	}
	
	if (img[1] != ':' || img[2] != '\\')
	{
		// it's not in the form "x:\blah\billy.jpg" so prepend plugin dir path.
		char temp[512];
		strcpy(temp, img);
		sprintf(img, "%s%s", m_szWinampPluginsPath, temp);
	}

	// 2. get color keys
	unsigned int ck_lo = (unsigned int)GetPrivateProfileInt(section, "colorkey_lo", 0x00000000, m_szImgIniFile);
	unsigned int ck_hi = (unsigned int)GetPrivateProfileInt(section, "colorkey_hi", 0x00202020, m_szImgIniFile);

	// 3. read in init code & per-frame code
	for (int n=0; n<2; n++)
	{
		char *pStr = (n==0) ? initcode : code;
		char szLineName[32];
		int len;

		int line = 1;
		int char_pos = 0;
		bool bDone = false;
		
		while (!bDone)
		{
			if (n==0)
				sprintf(szLineName, "init_%d", line);
			else
				sprintf(szLineName, "code_%d", line);


			GetPrivateProfileString(section, szLineName, "~!@#$", szTemp, 8192, m_szImgIniFile);	// fixme
			len = strlen(szTemp);

			if ((lstrcmp(szTemp, "~!@#$")==0) ||		// if the key was missing,
				(len >= 8191-char_pos-1))			// or if we're out of space
			{
				bDone = true;
			}
			else 
			{
				sprintf(&pStr[char_pos], "%s%c", szTemp, LINEFEED_CONTROL_CHAR);
			}
		
			char_pos += len + 1;
			line++;
		}
		pStr[char_pos++] = 0;	// null-terminate
	}

	if (nSlot == -1)
	{
		// find first empty slot; if none, chuck the oldest sprite & take its slot.
		int oldest_index = 0;
		int oldest_frame = m_texmgr.m_tex[0].nStartFrame;
		for (int x=0; x<NUM_TEX; x++)
		{
			if (!m_texmgr.m_tex[x].pSurface)
			{
				nSlot = x;
				break;
			}
			else if (m_texmgr.m_tex[x].nStartFrame < oldest_frame)
			{
				oldest_index = x;
				oldest_frame = m_texmgr.m_tex[x].nStartFrame;
			}
		}

		if (nSlot == -1)
		{
			nSlot = oldest_index;
			m_texmgr.KillTex(nSlot);
		}
	}

	int ret = m_texmgr.LoadTex(img, nSlot, initcode, code, m_fAnimTime, m_nFrames, ck_lo, ck_hi);
	m_texmgr.m_tex[nSlot].nUserData = nSpriteNum;

	m_fShowUserMessageUntilThisTime = m_fAnimTime + 6.0f;

	switch(ret & TEXMGR_ERROR_MASK)
	{
	case TEXMGR_ERR_SUCCESS:
		switch(ret & TEXMGR_WARNING_MASK)
		{
		case TEXMGR_WARN_ERROR_IN_INIT_CODE: sprintf(m_szUserMessage, "sprite #%d warning: error in initialization code ", nSpriteNum); break;
		case TEXMGR_WARN_ERROR_IN_REG_CODE:  sprintf(m_szUserMessage, "sprite #%d warning: error in per-frame code", nSpriteNum); break;
		default: 
			// success; no errors OR warnings.
			m_fShowUserMessageUntilThisTime = m_fAnimTime - 1.0f; 
			break;
		}
		break;
	case TEXMGR_ERR_BAD_INDEX:              sprintf(m_szUserMessage, "sprite #%d error: bad slot index", nSpriteNum); break;
	case TEXMGR_ERR_OPENING:                sprintf(m_szUserMessage, "sprite #%d error: unable to open imagefile", nSpriteNum); break;
	case TEXMGR_ERR_FORMAT:                 sprintf(m_szUserMessage, "sprite #%d error: file is corrupt or non-jpeg image", nSpriteNum); break;
	case TEXMGR_ERR_IMAGE_NOT_24_BIT:       sprintf(m_szUserMessage, "sprite #%d error: image does not have 3 color channels", nSpriteNum); break;
	case TEXMGR_ERR_IMAGE_TOO_LARGE:        sprintf(m_szUserMessage, "sprite #%d error: image is too large", nSpriteNum); break;
	case TEXMGR_ERR_CREATESURFACE_FAILED:   sprintf(m_szUserMessage, "sprite #%d error: createsurface() failed", nSpriteNum); break;
	case TEXMGR_ERR_LOCKSURFACE_FAILED:     sprintf(m_szUserMessage, "sprite #%d error: lock() failed", nSpriteNum); break;
	case TEXMGR_ERR_CORRUPT_JPEG:           sprintf(m_szUserMessage, "sprite #%d error: jpeg is corrupt", nSpriteNum); break;
	}

	return (ret & TEXMGR_ERROR_MASK) ? false : true;
}

void CMilkDropObj::KillSprite(int iSlot)
{
	m_texmgr.KillTex(iSlot);
}


